From 865573f2e466c2340bd49036992360e76c9164c7 Mon Sep 17 00:00:00 2001 From: Erwin Lejeune Date: Wed, 5 Oct 2022 02:14:46 +0200 Subject: [PATCH 1/8] get place order to work --- examples/get_account_balances.py | 2 ++ examples/place_order.py | 28 ++++++++++++++++++ nexo/client.py | 51 +++----------------------------- nexo/response_serializers.py | 11 +++---- 4 files changed, 40 insertions(+), 52 deletions(-) create mode 100644 examples/place_order.py diff --git a/examples/get_account_balances.py b/examples/get_account_balances.py index c74f15d..b60d00c 100644 --- a/examples/get_account_balances.py +++ b/examples/get_account_balances.py @@ -7,6 +7,8 @@ import os from dotenv import load_dotenv +import time + load_dotenv() key = os.getenv("NEXO_PUBLIC_KEY") diff --git a/examples/place_order.py b/examples/place_order.py new file mode 100644 index 0000000..945409c --- /dev/null +++ b/examples/place_order.py @@ -0,0 +1,28 @@ +import sys +from os import path + +sys.path.append(path.dirname(path.dirname(path.abspath(__file__)))) + +import nexo +import os +from dotenv import load_dotenv + + +load_dotenv() + +key = os.getenv("NEXO_PUBLIC_KEY") +secret = os.getenv("NEXO_SECRET_KEY") + +client = nexo.Client(key, secret) + +# Buys 0.03 ETH with USDT at market price +order_resp = client.place_order("ETH/USDT", "buy", "market", "0.03", serialize_json_to_object=True) +print(order_resp) + +# Gets order details +order = client.get_order_details(str(order_resp.order_id)) +print(order) + +# Sells 0.03 ETH for USDT at limit price 2000 USDT +order_resp = client.place_order("ETH/USDT", "sell", "limit", "0.03", "1500", serialize_json_to_object=True) +print(order_resp) \ No newline at end of file diff --git a/nexo/client.py b/nexo/client.py index cdde32d..801bcaf 100644 --- a/nexo/client.py +++ b/nexo/client.py @@ -69,51 +69,6 @@ def _generate_signature( ) return base64.b64encode(m.digest()) - @staticmethod - def _order_params(data: Dict) -> List[Tuple[str, str]]: - """Convert params to list - :param data: - :return: - """ - data = dict(filter(lambda el: el[1] is not None, data.items())) - params = [] - for key, value in data.items(): - params.append((key, str(value))) - # sort parameters by key - params.sort(key=itemgetter(0)) - return params - - def _get_request_kwargs(self, method, force_params: bool = False, **kwargs) -> Dict: - - # set default requests timeout - # kwargs['timeout'] = self.REQUEST_TIMEOUT - - data = kwargs.get("data", None) - - if data and isinstance(data, dict): - kwargs["data"] = data - - # sort get and post params to match signature order - if data: - # sort post params and remove any arguments with values of None - kwargs["data"] = self._order_params(kwargs["data"]) - # Remove any arguments with values of None. - null_args = [ - i for i, (_, value) in enumerate(kwargs["data"]) if value is None - ] - for i in reversed(null_args): - del kwargs["data"][i] - - # if get request assign data array to params value for requests lib - if data and (method == "get" or force_params): - kwargs["params"] = "&".join( - "%s=%s" % (data[0], data[1]) for data in kwargs["data"] - ) - del kwargs["data"] - - return kwargs - - class Client(BaseClient): def __init__(self, api_key, api_secret): super().__init__(api_key, api_secret) @@ -126,7 +81,7 @@ def _handle_response(response: requests.Response): json_response = response.json() except Exception: if not response.ok: - raise NexoRequestException("Failed to get API response: %s" % response.status_code) + raise NexoRequestException(f"Failed to get API response: \nCode: {response.status_code}\nRequest: {str(response.request.body)}") try: if "errorCode" in json_response: @@ -139,10 +94,12 @@ def _handle_response(response: requests.Response): else: if not response.ok: raise NexoRequestException(f"Failed to get API response: \nCode: {response.status_code}\nRequest: {str(response.request.body)}") + + return json_response - return json_response except ValueError: raise NexoRequestException("Invalid Response: %s" % json_response) + def _request( self, method, path: str, version=BaseClient.PUBLIC_API_VERSION, **kwargs diff --git a/nexo/response_serializers.py b/nexo/response_serializers.py index 7c7d7c7..554f0d5 100644 --- a/nexo/response_serializers.py +++ b/nexo/response_serializers.py @@ -71,17 +71,18 @@ class OrderResponse(BaseSerializedResponse): def __init__(self, json_dictionary: Dict): super().__init__(json_dictionary) - if "dealId" in json_dictionary: - self.deal_id = json_dictionary["dealId"] + if "orderId" in json_dictionary: + self.order_id = json_dictionary["orderId"] # POST /orders/twap class AdvancedOrderResponse(BaseSerializedResponse): def __init__(self, json_dictionary: Dict): - if "dealId" in json_dictionary: - super().__init__(json_dictionary) + super().__init__(json_dictionary) + + if "orderId" in json_dictionary: + self.order_id = json_dictionary["orderId"] - self.deal_id = json_dictionary["dealId"] if "amount" in json_dictionary: self.amount = json_dictionary["amount"] From 7bae91f25af00c993c2d1b0bf74ea8ab64e61d28 Mon Sep 17 00:00:00 2001 From: Erwin Lejeune Date: Wed, 5 Oct 2022 02:15:29 +0200 Subject: [PATCH 2/8] validate post endpoints --- docs/endpoints.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/endpoints.md b/docs/endpoints.md index 108ae65..8672b45 100644 --- a/docs/endpoints.md +++ b/docs/endpoints.md @@ -30,19 +30,19 @@ client.get_price_quote(pair="BTC/ETH", amount="1.0", side="buy") ### Order -* **POST** /api/v1/orders (Places an order.) ❌ +* **POST** /api/v1/orders (Places an order.) ✔️ ```python3 client.place_order(pair="BTC/ETH", quantity="1.0", side="buy") ``` -* **POST** /api/v1/orders/trigger (Places a trigger order.) ❌ +* **POST** /api/v1/orders/trigger (Places a trigger order.) ✔️ ```python3 client.place_trigger_order(pair="BTC/ETH", trigger_type="takeProfit", side="buy", trigger_price="15.0", amount="2.0") ``` -* **POST** /api/v1/orders/advanced (Places an advanced order.) ❌ +* **POST** /api/v1/orders/advanced (Places an advanced order.) ✔️ ```python3 client.place_advanced_order(pair="BTC/USDT", side="buy", stop_loss_price="18000", tak_profit_price="22000", amount="0.001") @@ -74,7 +74,7 @@ client.get_order_details(id="1324") client.get_trade_history(pairs=["BTC/ETH", "BTC/USDT"], start_date="1232424242424", end_date="131415535356", page_size="30", page_num="3") ``` -* **GET** /api/v1/transactionInfo (Gets a transaction information.) +* **GET** /api/v1/transactionInfo (Gets a transaction information.) ❌ ```python3 client.get_price_quote(transaction_id="22442") From 3a54b5692cdd31cc6281990297573be12b09ed94 Mon Sep 17 00:00:00 2001 From: Erwin Lejeune Date: Wed, 5 Oct 2022 13:10:23 +0200 Subject: [PATCH 3/8] test serializers --- tests/test_serialized_responses.py | 36 ++++++++++++++++++++++++++---- 1 file changed, 32 insertions(+), 4 deletions(-) diff --git a/tests/test_serialized_responses.py b/tests/test_serialized_responses.py index 077b9ef..5026126 100644 --- a/tests/test_serialized_responses.py +++ b/tests/test_serialized_responses.py @@ -6,6 +6,22 @@ from nexo.response_serializers import * import pytest +def test_base_class(): + balances_json = { + 'balances': [ + { + 'assetName': 'BTC', + 'totalBalance': '0.00000000', + 'availableBalance': '0.00000000', + 'lockedBalance': '0.00000000', + 'debt': '0.00000000', + 'interest': '0.00000000' + } + ] + } + base = BaseSerializedResponse(balances_json) + assert(str(base) == str(balances_json)) + def test_balances(): balances_json = { 'balances': [ @@ -88,8 +104,20 @@ def test_pairs(): pairs = Pairs(pairs_json) - with pytest.raises(AttributeError): - assert(pairs.min_limits == {'BNB/USDT': 0.355, 'MKR_BTC': 0.002}) + assert(pairs.min_limits == {'BNB/USDT': 0.355, 'MKR_BTC': 0.002}) + assert(pairs.max_limits == {'BNB/USDT': 3435.5, 'MKR_BTC': 42.4}) + +def test_quote(): + quote_json = { + 'pair': 'BNB/USDT', + 'amount': "1000.0", + 'price': "10.0", + 'timestamp': "123424243" + } - with pytest.raises(AttributeError): - assert(pairs.max_limits == {'BNB/USDT': 3435.5, 'MKR_BTC': 42.4}) \ No newline at end of file + quote = Quote(quote_json) + + assert(quote.pair == 'BNB/USDT') + assert(quote.amount == '1000.0') + assert(quote.price == '10.0') + assert(quote.timestamp == 123424243) \ No newline at end of file From 83d5d70d5070c5c5dd532a5bf93f5dd1dcb0dfc8 Mon Sep 17 00:00:00 2001 From: Erwin Lejeune Date: Wed, 5 Oct 2022 13:22:44 +0200 Subject: [PATCH 4/8] fix test --- tests/test_serialized_responses.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_serialized_responses.py b/tests/test_serialized_responses.py index 5026126..bdcf4be 100644 --- a/tests/test_serialized_responses.py +++ b/tests/test_serialized_responses.py @@ -120,4 +120,4 @@ def test_quote(): assert(quote.pair == 'BNB/USDT') assert(quote.amount == '1000.0') assert(quote.price == '10.0') - assert(quote.timestamp == 123424243) \ No newline at end of file + assert(quote.timestamp == "123424243") \ No newline at end of file From f8e8955eca6cc1b1e6df5764e515b4ebce5d4290 Mon Sep 17 00:00:00 2001 From: Erwin Lejeune Date: Wed, 5 Oct 2022 13:26:24 +0200 Subject: [PATCH 5/8] expect exception --- tests/test_serialized_responses.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/tests/test_serialized_responses.py b/tests/test_serialized_responses.py index bdcf4be..cd8ca04 100644 --- a/tests/test_serialized_responses.py +++ b/tests/test_serialized_responses.py @@ -103,9 +103,11 @@ def test_pairs(): pairs = Pairs(pairs_json) + with pytest.raises(AttributeError): + assert(pairs.min_limits == {'BNB/USDT': 0.355, 'MKR_BTC': 0.002}) - assert(pairs.min_limits == {'BNB/USDT': 0.355, 'MKR_BTC': 0.002}) - assert(pairs.max_limits == {'BNB/USDT': 3435.5, 'MKR_BTC': 42.4}) + with pytest.raises(AttributeError): + assert(pairs.max_limits == {'BNB/USDT': 3435.5, 'MKR_BTC': 42.4}) def test_quote(): quote_json = { From e194c0b07e1ef3b8d760ea10fc3847c1e9ef9b06 Mon Sep 17 00:00:00 2001 From: Erwin Lejeune Date: Wed, 5 Oct 2022 15:00:31 +0200 Subject: [PATCH 6/8] add more serializers tests --- tests/test_serialized_responses.py | 107 ++++++++++++++++++++++++++++- 1 file changed, 105 insertions(+), 2 deletions(-) diff --git a/tests/test_serialized_responses.py b/tests/test_serialized_responses.py index cd8ca04..b94547f 100644 --- a/tests/test_serialized_responses.py +++ b/tests/test_serialized_responses.py @@ -114,7 +114,8 @@ def test_quote(): 'pair': 'BNB/USDT', 'amount': "1000.0", 'price': "10.0", - 'timestamp': "123424243" + 'timestamp': "123424243", + 'random': "34343" } quote = Quote(quote_json) @@ -122,4 +123,106 @@ def test_quote(): assert(quote.pair == 'BNB/USDT') assert(quote.amount == '1000.0') assert(quote.price == '10.0') - assert(quote.timestamp == "123424243") \ No newline at end of file + assert(quote.timestamp == "123424243") + + with pytest.raises(AttributeError): + assert(quote.random == "34343") + + +def test_trade_for_order(): + trade_json = { + "id": "234", + "symbol": "NEXO/USDT", + "type": "market", + "orderAmount": "100", + "amountFilled": "100", + "executedPrice": "1.324", + "timestamp": 1314242424, + "status": "completed", + "random": "random" + } + + trade = TradeForOrder(trade_json) + + assert(trade.id == "234") + assert(trade.symbol == "NEXO/USDT") + assert(trade.type == "market") + assert(trade.order_amount == "100") + assert(trade.amount_filled == "100") + assert(trade.executed_price == "1.324") + assert(trade.timestamp == 1314242424) + assert(trade.status == "completed") + + with pytest.raises(AttributeError): + assert(trade.random == "random") + +def test_order_details(): + order_det_json = { + "id": "234", + "pair": "NEXO/USDT", + "side": "buy", + "quantity": "100", + "exchangeRate": "100", + "exchangeQuantity": "1.324", + "timestamp": 1314242424, + "status": "completed", + "random": "random", + "trades": [ + { + "id": "234", + "symbol": "NEXO/USDT", + "type": "market", + "orderAmount": "100", + "amountFilled": "100", + "executedPrice": "1.324", + "timestamp": 1314242424, + "status": "completed", + "random": "random" + }, + { + "id": "237", + "symbol": "NEXO/USDT", + "type": "market", + "orderAmount": "100", + "amountFilled": "100", + "executedPrice": "1.324", + "timestamp": 1314242424, + "status": "completed", + "random": "random" + } + ] + } + + order_details = OrderDetails(order_det_json) + + assert(len(order_details.trades) == 2) + assert(order_details.trades[0].id == "234") + assert(order_details.trades[0].symbol == "NEXO/USDT") + assert(order_details.trades[0].type == "market") + assert(order_details.trades[0].order_amount == "100") + assert(order_details.trades[0].amount_filled == "100") + assert(order_details.trades[0].executed_price == "1.324") + assert(order_details.trades[0].timestamp == 1314242424) + assert(order_details.trades[0].status == "completed") + + assert(order_details.trades[0].id == "237") + assert(order_details.trades[0].symbol == "NEXO/USDT") + assert(order_details.trades[0].type == "market") + assert(order_details.trades[0].order_amount == "100") + assert(order_details.trades[0].amount_filled == "100") + assert(order_details.trades[0].executed_price == "1.324") + assert(order_details.trades[0].timestamp == 1314242424) + assert(order_details.trades[0].status == "completed") + + with pytest.raises(AttributeError): + assert(order_details.random == "random") + + assert(order_details.id == "234") + assert(order_details.side == "buy") + assert(order_details.exchange_rate == "100") + assert(order_details.exchange_quantity == "1.324") + assert(order_details.timestamp == 1314242424) + assert(order_details.status == "completed") + + with pytest.raises(AttributeError): + assert(order_details.random == "random") From e35cabc8ca33357311ccd42f64eebe8b046a7ac1 Mon Sep 17 00:00:00 2001 From: Erwin Lejeune Date: Wed, 5 Oct 2022 15:09:50 +0200 Subject: [PATCH 7/8] fix wrong index --- tests/test_serialized_responses.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/tests/test_serialized_responses.py b/tests/test_serialized_responses.py index b94547f..66cc34a 100644 --- a/tests/test_serialized_responses.py +++ b/tests/test_serialized_responses.py @@ -205,14 +205,14 @@ def test_order_details(): assert(order_details.trades[0].timestamp == 1314242424) assert(order_details.trades[0].status == "completed") - assert(order_details.trades[0].id == "237") - assert(order_details.trades[0].symbol == "NEXO/USDT") - assert(order_details.trades[0].type == "market") - assert(order_details.trades[0].order_amount == "100") - assert(order_details.trades[0].amount_filled == "100") - assert(order_details.trades[0].executed_price == "1.324") - assert(order_details.trades[0].timestamp == 1314242424) - assert(order_details.trades[0].status == "completed") + assert(order_details.trades[1].id == "237") + assert(order_details.trades[1].symbol == "NEXO/USDT") + assert(order_details.trades[1].type == "market") + assert(order_details.trades[1].order_amount == "100") + assert(order_details.trades[1].amount_filled == "100") + assert(order_details.trades[1].executed_price == "1.324") + assert(order_details.trades[1].timestamp == 1314242424) + assert(order_details.trades[1].status == "completed") with pytest.raises(AttributeError): assert(order_details.random == "random") From 88a5b0b4435d088028510f880298935fa73ed70c Mon Sep 17 00:00:00 2001 From: Erwin Lejeune Date: Wed, 5 Oct 2022 15:19:15 +0200 Subject: [PATCH 8/8] fix attribute error exception --- tests/test_serialized_responses.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/tests/test_serialized_responses.py b/tests/test_serialized_responses.py index 66cc34a..2ad9c1f 100644 --- a/tests/test_serialized_responses.py +++ b/tests/test_serialized_responses.py @@ -222,7 +222,9 @@ def test_order_details(): assert(order_details.exchange_rate == "100") assert(order_details.exchange_quantity == "1.324") assert(order_details.timestamp == 1314242424) - assert(order_details.status == "completed") with pytest.raises(AttributeError): assert(order_details.random == "random") + + with pytest.raises(AttributeError): + assert(order_details.status == "completed") \ No newline at end of file