diff --git a/fin_depo/data.py b/fin_depo/data.py index fc83da4..9ee66fa 100644 --- a/fin_depo/data.py +++ b/fin_depo/data.py @@ -134,3 +134,30 @@ class TradeOrderDetails: order_id: object raw_order_details: object + +@enforce_typing.enforce_types +@dataclasses.dataclass(frozen=True) +class WithdrawalDetails: + withdrawn_asset: Asset + withdrawn_amount: Decimal + + fee_asset: Asset + fee_amount: Decimal + + executed_time: datetime.datetime + + raw_details: object + +@enforce_typing.enforce_types +@dataclasses.dataclass(frozen=True) +class DepositDetails: + deposit_asset: Asset + deposit_amount: Decimal + + fee_asset: Asset + fee_amount: Decimal + + executed_time: datetime.datetime + + raw_details: object + diff --git a/fin_depo/defi_kucoin.py b/fin_depo/defi_kucoin.py index 1289097..b3b7e55 100644 --- a/fin_depo/defi_kucoin.py +++ b/fin_depo/defi_kucoin.py @@ -8,7 +8,8 @@ from decimal import Decimal import fin_defs import kucoin.client -from .data import DepoFetcher, DepoGroup, DepoSingle, TradeOrderDetails +from .data import (DepoFetcher, DepoGroup, DepoSingle, TradeOrderDetails, + WithdrawalDetails, DepositDetails) logger = logging.getLogger(__name__) @@ -146,6 +147,50 @@ class KucoinDepoFetcher(DepoFetcher): # Determine order details return self._get_order_details(response['orderId'], input_asset, output_asset) + def _get_withdrawals(self) -> list[WithdrawalDetails]: + raw_details = self.kucoin_client.get_withdrawals() + + withdrawals = [] + for item in raw_details['items']: + withdrawn_asset = parse_asset_from_ticker(item['currency']) + withdrawn_amount = Decimal(item['amount']) + fee_asset = withdrawn_asset # Assumed + fee_amount = Decimal(item['fee']) + executed_time=datetime.datetime.fromtimestamp( + item['createdAt'] / 1000, + tz=datetime.UTC, + ) + + withdrawals.append(WithdrawalDetails(withdrawn_asset, + withdrawn_amount, fee_asset, + fee_amount, executed_time, + item)) + del item + + return withdrawals + + def _get_deposits(self) -> list[WithdrawalDetails]: + raw_details = self.kucoin_client.get_deposits() + + deposits = [] + for item in raw_details['items']: + deposit_asset = parse_asset_from_ticker(item['currency']) + deposit_amount = Decimal(item['amount']) + fee_asset = deposit_asset # Assumed + fee_amount = Decimal(item['fee']) + executed_time=datetime.datetime.fromtimestamp( + item['createdAt'] / 1000, + tz=datetime.UTC, + ) + + deposits.append(DepositDetails(deposit_asset, + deposit_amount, fee_asset, + fee_amount, executed_time, + item)) + del item + + return deposits + def _get_order_details( self, order_id: str, diff --git a/test/test_kucoin.py b/test/test_kucoin.py index e8932b7..e8923ff 100644 --- a/test/test_kucoin.py +++ b/test/test_kucoin.py @@ -19,106 +19,30 @@ fin_depo.defi_kucoin.logger.setLevel('INFO') NOW = datetime.datetime.now(tz=datetime.UTC) - -@needs_secrets -def test_get_depo(): - """Can inspect kucoin depository.""" - fetcher = fin_depo.defi_kucoin.KucoinDepoFetcher( +def get_fetcher() -> fin_depo.defi_kucoin.KucoinDepoFetcher: + return fin_depo.defi_kucoin.KucoinDepoFetcher( secrets.KUCOIN_KEY, secrets.KUCOIN_SECRET, secrets.KUCOIN_PASS, ) - depo = fetcher.get_depo() +@needs_secrets +def test_get_depo(): + """Can inspect kucoin depository.""" + depo = get_fetcher().get_depo() assert isinstance(depo, fin_depo.data.DepoGroup) for nested_depo in depo.nested: assert isinstance(nested_depo, fin_depo.data.DepoSingle) +@needs_secrets +def test_get_withdrawals(): + withdrawals = get_fetcher()._get_withdrawals() + print(withdrawals) + assert len(withdrawals) > 0 @needs_secrets -def test_place_market_order_requires_allow_trades(): - """Client fails if allow_trades is not enabled when doing market orders.""" - fetcher = fin_depo.defi_kucoin.KucoinDepoFetcher( - secrets.KUCOIN_KEY, - secrets.KUCOIN_SECRET, - secrets.KUCOIN_PASS, - allow_trades=False, - ) - - with pytest.raises(PermissionError) as m: - fetcher.place_market_order(fin_defs.MPC, Decimal(1), fin_defs.USDT) - - assert 'KucoinDepoFetcher.allow_trades is not enabled: Cannot make trades' in str(m) - - -@needs_secrets -def test_place_buy_side_order(): - """Client can place buy side market orders.""" - fetcher = fin_depo.defi_kucoin.KucoinDepoFetcher( - secrets.KUCOIN_KEY, - secrets.KUCOIN_SECRET, - secrets.KUCOIN_PASS, - allow_trades=TEST_MARKET_ORDERS, - ) - - input_amount = Decimal('0.1') - - order_details = fetcher.place_market_order( - fin_defs.USDT, - input_amount, - fin_defs.MPC, - ) - - assert order_details is not None - assert order_details.order_id is not None - assert order_details.raw_order_details is not None - - assert order_details.input_asset == fin_defs.USDT - assert order_details.output_asset == fin_defs.MPC - - assert order_details.input_amount <= input_amount - assert order_details.output_amount >= 0 - - assert order_details.input_amount != order_details.output_amount - - assert order_details.fee_asset == fin_defs.USDT - assert order_details.fee_amount <= Decimal('0.0002') - - assert NOW <= order_details.executed_time <= NOW + datetime.timedelta(minutes=10) - - -@needs_secrets -def test_place_sell_side_order(): - """Client can place sell side market orders.""" - fetcher = fin_depo.defi_kucoin.KucoinDepoFetcher( - secrets.KUCOIN_KEY, - secrets.KUCOIN_SECRET, - secrets.KUCOIN_PASS, - allow_trades=TEST_MARKET_ORDERS, - ) - - input_amount = Decimal('1') - - order_details = fetcher.place_market_order( - fin_defs.MPC, - input_amount, - fin_defs.USDT, - ) - - assert order_details is not None - assert order_details.order_id is not None - assert order_details.raw_order_details is not None - - assert order_details.input_asset == fin_defs.MPC - assert order_details.output_asset == fin_defs.USDT - - assert order_details.input_amount <= input_amount - assert order_details.output_amount >= 0 - - assert order_details.input_amount != order_details.output_amount - - assert order_details.fee_asset == fin_defs.USDT - assert order_details.fee_amount is not None - - assert NOW <= order_details.executed_time <= NOW + datetime.timedelta(minutes=10) +def test_get_deposits(): + deposits = get_fetcher()._get_deposits() + print(deposits) + assert len(deposits) > 0 diff --git a/test/test_kucoin_trading.py b/test/test_kucoin_trading.py new file mode 100644 index 0000000..0bbb24c --- /dev/null +++ b/test/test_kucoin_trading.py @@ -0,0 +1,109 @@ +import datetime +from decimal import Decimal + +import fin_defs +import pytest + +import fin_depo + +from . import secrets + +TEST_MARKET_ORDERS = True + +needs_secrets = pytest.mark.skipif( + not secrets.KUCOIN_KEY, + reason='Secret kucoin_USERNAME required', +) + +fin_depo.defi_kucoin.logger.setLevel('INFO') + +NOW = datetime.datetime.now(tz=datetime.UTC) + + +@needs_secrets +def test_place_market_order_requires_allow_trades(): + """Client fails if allow_trades is not enabled when doing market orders.""" + fetcher = fin_depo.defi_kucoin.KucoinDepoFetcher( + secrets.KUCOIN_KEY, + secrets.KUCOIN_SECRET, + secrets.KUCOIN_PASS, + allow_trades=False, + ) + + with pytest.raises(PermissionError) as m: + fetcher.place_market_order(fin_defs.MPC, Decimal(1), fin_defs.USDT) + + assert 'KucoinDepoFetcher.allow_trades is not enabled: Cannot make trades' in str(m) + + +@needs_secrets +def test_place_buy_side_order(): + """Client can place buy side market orders.""" + fetcher = fin_depo.defi_kucoin.KucoinDepoFetcher( + secrets.KUCOIN_KEY, + secrets.KUCOIN_SECRET, + secrets.KUCOIN_PASS, + allow_trades=TEST_MARKET_ORDERS, + ) + + input_amount = Decimal('0.1') + + order_details = fetcher.place_market_order( + fin_defs.USDT, + input_amount, + fin_defs.MPC, + ) + + assert order_details is not None + assert order_details.order_id is not None + assert order_details.raw_order_details is not None + + assert order_details.input_asset == fin_defs.USDT + assert order_details.output_asset == fin_defs.MPC + + assert order_details.input_amount <= input_amount + assert order_details.output_amount >= 0 + + assert order_details.input_amount != order_details.output_amount + + assert order_details.fee_asset == fin_defs.USDT + assert order_details.fee_amount <= Decimal('0.0002') + + assert NOW <= order_details.executed_time <= NOW + datetime.timedelta(minutes=10) + + +@needs_secrets +def test_place_sell_side_order(): + """Client can place sell side market orders.""" + fetcher = fin_depo.defi_kucoin.KucoinDepoFetcher( + secrets.KUCOIN_KEY, + secrets.KUCOIN_SECRET, + secrets.KUCOIN_PASS, + allow_trades=TEST_MARKET_ORDERS, + ) + + input_amount = Decimal('1') + + order_details = fetcher.place_market_order( + fin_defs.MPC, + input_amount, + fin_defs.USDT, + ) + + assert order_details is not None + assert order_details.order_id is not None + assert order_details.raw_order_details is not None + + assert order_details.input_asset == fin_defs.MPC + assert order_details.output_asset == fin_defs.USDT + + assert order_details.input_amount <= input_amount + assert order_details.output_amount >= 0 + + assert order_details.input_amount != order_details.output_amount + + assert order_details.fee_asset == fin_defs.USDT + assert order_details.fee_amount is not None + + assert NOW <= order_details.executed_time <= NOW + datetime.timedelta(minutes=10) +