1
0
fin-depo/fin_depo/defi_kucoin.py

195 lines
7.0 KiB
Python
Raw Permalink Normal View History

2024-06-20 21:43:45 +00:00
"""See `KucoinDepoFetcher` for documentation."""
2024-05-29 20:29:15 +00:00
import datetime
import logging
2024-07-27 01:14:12 +00:00
import time
2024-05-29 20:29:15 +00:00
from decimal import Decimal
2024-06-02 14:14:46 +00:00
import fin_defs
2024-05-29 20:29:15 +00:00
import kucoin.client
from .data import DepoFetcher, DepoGroup, DepoSingle, TradeOrderDetails
2024-05-29 20:29:15 +00:00
logger = logging.getLogger(__name__)
2024-07-18 22:22:29 +00:00
class KucoinDepoFetcher(DepoFetcher):
"""`Depo` fetcher for [Kucoin](https://www.kucoin.com), the online crypto currency exchange.
2024-06-20 21:43:45 +00:00
Requirements for use:
- Account on [Kucoin](https://www.kucoin.com).
- Have performed Know Your Customer (KYC) for your account.
- Created API key from [settings menu](https://www.kucoin.com/account/api).
API key must have the **General Permission**. Unless you are using the
`place_market_order` functionality, the API key **should not have
2024-06-20 21:43:45 +00:00
any additional permissions**. Employ principle of least priviledge.
2024-07-18 22:55:12 +00:00
- Install [`python-kucoin`](https://python-kucoin.readthedocs.io/en/latest/) library.
2024-06-20 21:43:45 +00:00
Depository structure: An upper level depo split for each of the
sub-accounts (Funding, Trading, Margin, Futures...)
"""
def __init__(
self,
kucoin_key: str,
kucoin_secret: str,
kucoin_pass: str,
allow_trades: bool = False,
):
2024-07-18 22:22:29 +00:00
self.assert_param('kucoin_key', str, kucoin_key)
self.assert_param('kucoin_secret', str, kucoin_secret)
self.assert_param('kucoin_pass', str, kucoin_pass)
self.allow_trades = allow_trades
self.kucoin_client = kucoin.client.Client(
2024-05-29 20:29:15 +00:00
kucoin_key,
kucoin_secret,
kucoin_pass,
)
def get_depo(self) -> DepoGroup:
2024-07-18 23:24:59 +00:00
# We would ideally get timestamp from request,
# but this is fine for now.
2024-05-29 20:29:15 +00:00
now = datetime.datetime.now(tz=datetime.UTC)
2024-07-18 23:24:59 +00:00
# Assets are spread across account types, but we would like them
# clustered into different depos.
2024-05-29 20:29:15 +00:00
assets_by_account_type: dict[str, dict[fin_defs.Asset, Decimal]] = {}
2024-07-18 23:24:59 +00:00
for account_data in self.kucoin_client.get_accounts():
2024-05-29 20:29:15 +00:00
asset = fin_defs.WELL_KNOWN_SYMBOLS[account_data['currency']]
2024-06-02 14:14:46 +00:00
balance = Decimal(account_data['balance'])
2024-06-02 16:33:25 +00:00
assets_for_account_type = assets_by_account_type.setdefault(
2024-06-02 15:24:53 +00:00
account_data['type'],
{},
2024-06-02 14:14:46 +00:00
)
assets_for_account_type[asset] = (
assets_for_account_type.get(asset, Decimal(0)) + balance
)
2024-06-02 16:33:25 +00:00
del account_data, asset, balance, assets_for_account_type
2024-07-18 23:24:59 +00:00
2024-06-02 14:14:46 +00:00
return DepoGroup(
'Kucoin',
now,
[
DepoSingle('Kucoin ' + account_type, now, assets)
for account_type, assets in assets_by_account_type.items()
],
)
def place_market_order(
self,
input_asset: fin_defs.Asset,
input_amount: Decimal,
output_asset: fin_defs.Asset,
) -> TradeOrderDetails:
"""Executes a market order through the Kucoin market.
Will automatically determine the market based on the input and output
assets.
Requirements:
- Fetcher must have been created with `allow_trades=True`.
- API key used with fetcher must have **Spot Trading** permissions.
- Assets must be on trading account. Assets on funding accounts or
other accounts cannot be used.
Note:
2024-07-22 20:01:34 +00:00
- A fee will be paid to Kucoin, with the rate determined by your WIP
level and the asset being traded.
- The full `input_amount` may not be used. Inspect the resulting
`TradeOrderDetails` to see how much of the `input_amount` have been
used.
2024-07-22 20:01:34 +00:00
References:
- POST Market Order: <https://www.kucoin.com/docs/rest/spot-trading/orders/place-order>
- GET Market Order by id: <https://www.kucoin.com/docs/rest/spot-trading/orders/get-order-details-by-orderid>
"""
# Check requirements
if not self.allow_trades:
msg = 'KucoinDepoFetcher.allow_trades is not enabled: Cannot make trades'
raise PermissionError(msg)
assert fin_defs.USDT in [input_asset, output_asset], 'USDT markets only for now'
# Convert arguments to kucoin client arguments
if input_asset == fin_defs.USDT:
2024-09-02 18:15:08 +00:00
symbol: str = f'{output_asset.raw_short_name()}-{input_asset.raw_short_name()}'
side: str = 'buy'
size = None
funds = str(input_amount)
else:
2024-09-02 18:15:08 +00:00
symbol = f'{input_asset.raw_short_name()}-{output_asset.raw_short_name()}'
side = 'sell'
size = str(input_amount)
funds = None
# Place order
logger.info('Placing order: %s', str([symbol, side, size, funds]))
response = self.kucoin_client.create_market_order(
symbol=symbol,
side=side,
size=size,
funds=funds,
)
del symbol, side, size, funds, input_amount
# Determine order details
return self._get_order_details(response['orderId'], input_asset, output_asset)
2024-07-27 01:14:12 +00:00
def _get_order_details(
self,
order_id: str,
input_asset: fin_defs.Asset,
output_asset: fin_defs.Asset,
2024-07-27 01:14:12 +00:00
) -> TradeOrderDetails:
"""Determine the order details for the order with the given id.
Retries the order a few times, as KuCoin might not have propagated the
order through their systems.
"""
order_details = self._get_order_with_retries(order_id, num_retries=10)
# Convert from kucoin results
if input_asset == fin_defs.USDT:
input_amount_final = Decimal(order_details['dealFunds'])
output_amount_final = Decimal(order_details['dealSize'])
else:
output_amount_final = Decimal(order_details['dealFunds'])
input_amount_final = Decimal(order_details['dealSize'])
return TradeOrderDetails(
input_asset=input_asset,
input_amount=input_amount_final,
output_asset=output_asset,
output_amount=output_amount_final,
fee_asset=fin_defs.WELL_KNOWN_SYMBOLS[order_details['feeCurrency']],
fee_amount=Decimal(order_details['fee']),
2024-07-22 20:01:34 +00:00
executed_time=datetime.datetime.fromtimestamp(
order_details['createdAt'] / 1000,
tz=datetime.UTC,
2024-07-22 20:01:34 +00:00
),
order_id=order_id,
raw_order_details=order_details,
)
2024-07-27 01:14:12 +00:00
def _get_order_with_retries(
self,
order_id: str,
*,
num_retries: int,
sleep_between_tries: float = 1.0,
2024-07-27 01:14:12 +00:00
) -> dict:
"""Get the order details from KuCoin backend.
Retries the order a few times, as KuCoin might not have propagated the
order through their systems since it was sent.
"""
for _ in range(num_retries):
try:
return self.kucoin_client.get_order(order_id)
2024-07-27 01:14:12 +00:00
except kucoin.exceptions.KucoinAPIException as e: # noqa
time.sleep(sleep_between_tries)
return self.kucoin_client.get_order(order_id)