From cf2cf98051f67f81b883766ae4a313c2b7207c96 Mon Sep 17 00:00:00 2001 From: Morty Space Date: Sat, 14 Nov 2020 02:34:07 +0200 Subject: [PATCH] Fixed missing trades, updated coins --- README.md | 1 + src/cryptocom/exchange/__init__.py | 2 +- src/cryptocom/exchange/api.py | 2 +- src/cryptocom/exchange/coins.py | 2 + src/cryptocom/exchange/pairs.py | 4 ++ src/cryptocom/exchange/private.py | 22 ++++++-- src/cryptocom/exchange/structs.py | 84 ++++++++++++++++-------------- tests/test_market.py | 1 + tests/test_private.py | 7 +++ 9 files changed, 80 insertions(+), 45 deletions(-) diff --git a/README.md b/README.md index c37f2c3..f663d32 100644 --- a/README.md +++ b/README.md @@ -28,6 +28,7 @@ Exchange original API docs: [https://exchange-docs.crypto.com](https://exchange- ### Changelog +- **0.7.3** - fixed price of order if not filled, updated coins, added missing trades to `Order` - **0.7.2** - fixed `listen_orders` private account method, added test - **0.7.1** - fixed missing '.0' in order price and quantity (different in py3.7, py3.9) - **0.7** - major changes, `Pair` -> `cro.pairs.CRO_USDT` moved to more complex structure so we can use round and server information about pairs. diff --git a/src/cryptocom/exchange/__init__.py b/src/cryptocom/exchange/__init__.py index 071cb80..62a0bd9 100644 --- a/src/cryptocom/exchange/__init__.py +++ b/src/cryptocom/exchange/__init__.py @@ -14,4 +14,4 @@ __all__ = [ 'ApiError', 'ApiProvider' ] -__version__ = '0.7.2' +__version__ = '0.7.3' diff --git a/src/cryptocom/exchange/api.py b/src/cryptocom/exchange/api.py index 849f908..65cd474 100644 --- a/src/cryptocom/exchange/api.py +++ b/src/cryptocom/exchange/api.py @@ -140,7 +140,7 @@ class ApiProvider: try: async for data in self.listen_once(url, *channels, sign=sign): yield data - except: + except Exception: await asyncio.sleep(1) async def listen_once(self, url, *channels, sign=False): diff --git a/src/cryptocom/exchange/coins.py b/src/cryptocom/exchange/coins.py index a3fdce7..4c30655 100644 --- a/src/cryptocom/exchange/coins.py +++ b/src/cryptocom/exchange/coins.py @@ -37,6 +37,8 @@ EGLD = Coin("EGLD") USDC = Coin("USDC") ADA = Coin("ADA") ICX = Coin("ICX") +DOT = Coin("DOT") +REN = Coin("REN") def all(): diff --git a/src/cryptocom/exchange/pairs.py b/src/cryptocom/exchange/pairs.py index 054c16d..44caa43 100644 --- a/src/cryptocom/exchange/pairs.py +++ b/src/cryptocom/exchange/pairs.py @@ -83,6 +83,10 @@ EGLD_USDT = Pair("EGLD_USDT", 4, 3) WBTC_USDT = Pair("WBTC_USDT", 2, 6) QTUM_USDT = Pair("QTUM_USDT", 4, 4) BAND_USDT = Pair("BAND_USDT", 4, 2) +REN_CRO = Pair("REN_CRO", 4, 2) +DOT_USDT = Pair("DOT_USDT", 4, 3) +REN_USDT = Pair("REN_USDT", 5, 2) +DOT_CRO = Pair("DOT_CRO", 3, 3) def all(): diff --git a/src/cryptocom/exchange/private.py b/src/cryptocom/exchange/private.py index 2a3820c..22d3c8b 100644 --- a/src/cryptocom/exchange/private.py +++ b/src/cryptocom/exchange/private.py @@ -185,12 +185,24 @@ class Account: async def get_order(self, order_id: int) -> Order: """Get order info.""" - data = await self.api.post('private/get-order-detail', { - 'params': {'order_id': str(order_id)} - }) - order_info = data['order_info'] + order_info = {} + retries = 0 + + while True: + data = await self.api.post('private/get-order-detail', { + 'params': {'order_id': str(order_id)} + }) + order_info = data.get('order_info', {}) + if not order_info and retries < 10: + await asyncio.sleep(0.5) + retries += 1 + else: + break + return Order.create_from_api( - self.pairs[order_info['instrument_name']], order_info) + self.pairs[order_info['instrument_name']], + order_info, data['trade_list'] + ) async def cancel_order( self, order_id: int, pair: Pair, check_status=False) -> None: diff --git a/src/cryptocom/exchange/structs.py b/src/cryptocom/exchange/structs.py index b814c7f..6e4f0f9 100644 --- a/src/cryptocom/exchange/structs.py +++ b/src/cryptocom/exchange/structs.py @@ -203,6 +203,41 @@ class OrderForceType(str, Enum): IMMEDIATE_OR_CANCEL = 'IMMEDIATE_OR_CANCEL' +@dataclass +class PrivateTrade: + id: int + side: OrderSide + pair: Pair + fees: float + fees_coin: Coin + created_at: int + filled_price: float + filled_quantity: float + order_id: int + + @cached_property + def is_buy(self): + return self.side == OrderSide.BUY + + @cached_property + def is_sell(self): + return self.side == OrderSide.SELL + + @classmethod + def create_from_api(cls, pair: Pair, data: Dict) -> 'PrivateTrade': + return cls( + id=int(data['trade_id']), + side=OrderSide(data['side']), + pair=pair, + fees=round_up(data['fee'], 4), + fees_coin=Coin(data['fee_currency']), + created_at=int(data['create_time'] / 1000), + filled_price=pair.round_price(data['traded_price']), + filled_quantity=pair.round_quantity(data['traded_quantity']), + order_id=int(data['order_id']) + ) + + @dataclass class Order: id: int @@ -220,6 +255,7 @@ class Order: fees_coin: Coin force_type: OrderForceType trigger_price: float + trades: List[PrivateTrade] @cached_property def is_buy(self): @@ -270,18 +306,24 @@ class Order: return self.quantity - self.filled_quantity @classmethod - def create_from_api(cls, pair: Pair, data: dict) -> 'Order': + def create_from_api( + cls, pair: Pair, data: Dict, trades: List[Dict] = None) -> 'Order': fees_coin, trigger_price = None, None if data['fee_currency']: fees_coin = Coin(data['fee_currency']) if data.get('trigger_price') is not None: trigger_price = pair.round_price(data['trigger_price']) + trades = [ + PrivateTrade.create_from_api(pair, trade) + for trade in trades or [] + ] + return cls( id=int(data['order_id']), status=OrderStatus(data['status']), side=OrderSide(data['side']), - price=pair.round_price(data['price']), + price=pair.round_price(data['avg_price'] or data['price']), quantity=pair.round_quantity(data['quantity']), client_id=data['client_oid'], created_at=int(data['create_time'] / 1000), @@ -292,40 +334,6 @@ class Order: filled_quantity=pair.round_quantity(data['cumulative_quantity']), fees_coin=fees_coin, force_type=OrderForceType(data['time_in_force']), - trigger_price=trigger_price - ) - - -@dataclass -class PrivateTrade: - id: int - side: OrderSide - pair: Pair - fees: float - fees_coin: Coin - created_at: int - filled_price: float - filled_quantity: float - order_id: int - - @cached_property - def is_buy(self): - return self.side == OrderSide.BUY - - @cached_property - def is_sell(self): - return self.side == OrderSide.SELL - - @classmethod - def create_from_api(cls, pair: Pair, data: Dict) -> 'PrivateTrade': - return cls( - id=int(data['trade_id']), - side=OrderSide(data['side']), - pair=pair, - fees=round_up(data['fee'], 4), - fees_coin=Coin(data['fee_currency']), - created_at=int(data['create_time'] / 1000), - filled_price=pair.round_price(data['traded_price']), - filled_quantity=pair.round_quantity(data['traded_quantity']), - order_id=int(data['order_id']) + trigger_price=trigger_price, + trades=trades ) diff --git a/tests/test_market.py b/tests/test_market.py index cd3d96c..3e5506d 100644 --- a/tests/test_market.py +++ b/tests/test_market.py @@ -6,6 +6,7 @@ import cryptocom.exchange as cro @pytest.mark.asyncio async def test_get_pairs(exchange: cro.Exchange): pairs = await exchange.get_pairs() + assert sorted(exchange.pairs.keys()) == sorted(p.name for p in pairs) local_pairs = sorted(cro.pairs.all(), key=lambda p: p.name) server_pairs = sorted(pairs, key=lambda p: p.name) assert len(local_pairs) == len(server_pairs) diff --git a/tests/test_private.py b/tests/test_private.py index 0a3a233..93b0488 100644 --- a/tests/test_private.py +++ b/tests/test_private.py @@ -121,6 +121,13 @@ async def test_account_market_orders( ]) await asyncio.sleep(1) + orders = await asyncio.gather(*[ + account.get_order(order_id) + for order_id in order_ids['buy'] + order_ids['sell'] + ]) + for order in orders: + assert order.trades, order + trades = await account.get_trades(cro.pairs.CRO_USDT, page_size=20) for trade in trades: if trade.is_buy: