async def test_message_handling_of_stop_order_events(): """Tests handling of messages of the trading channel""" async def handle_message(json_message): LOG.debug("ignored message %s", json_message) client = AdvancedBitpandaProWebsocketClient(None, 'test', handle_message) client.apply_trading_buffer() order_creation = '{"order":{"time_in_force":"GOOD_TILL_CANCELLED","is_post_only":false,"trigger_price":"359.71",' \ '"order_id":"c02b49a0-312b-42e4-803b-5ff2179c3d5f",' \ '"account_id":"379a12c0-4560-11e9-82fe-2b25c6f7d123","instrument_code":"ETH_EUR",' \ '"time":"2020-08-07T14:21:53.691Z","side":"BUY","price":"359.71","amount":"1.0",' \ '"filled_amount":"0.0","type":"STOP"},"channel_name":"ORDERS","type":"ORDER_CREATED",' \ '"time":"2020-08-07T14:21:53.691Z"}' await client.handle_message(json.loads(order_creation)) assert len(client.state.open_orders_by_order_id) == 1 stop_order_tracked = '{"order_book_sequence": 0, "trigger_price": "359.71", "current_price": "359.78", ' \ '"instrument_code": "ETH_EUR", "order_id": "c02b49a0-312b-42e4-803b-5ff2179c3d5f", ' \ '"remaining": "1.0", "channel_name": "TRADING", "type": "TRACKED", ' \ '"time": "2020-08-07T14:21:53.692Z"} ' await client.handle_message(json.loads(stop_order_tracked)) assert len(client.state.open_orders_by_order_id) == 1 stop_order_triggered = '{"order_book_sequence": 1, "price": "359.71", "instrument_code": "ETH_EUR", "order_id": ' \ '"c02b49a0-312b-42e4-803b-5ff2179c3d5f", "remaining": "1.0", "channel_name": "TRADING", ' \ '"type": "TRIGGERED", "time": "2020-08-14T14:22:03.064Z"} ' await client.handle_message(json.loads(stop_order_triggered)) assert len(client.state.open_orders_by_order_id) == 1
async def test_handling_of_done_error_events(): """Tests handling of error messages where type is DONE""" async def handle_message(json_message): LOG.debug("ignored message %s", json_message) client = AdvancedBitpandaProWebsocketClient(None, 'test', handle_message) client.apply_trading_buffer() # Matching order would have caused a self trade => so it is never booked self_trade_prevented = '{"status": "SELF_TRADE", "instrument_code": "ETH_EUR", "order_id": ' \ '"338b7543-95d3-4cb8-a264-ed4212da7d92", "client_id": ' \ '"58672b66-fc1b-4855-add7-5365297a7213", "remaining": "60.6767", "channel_name": ' \ '"TRADING", "type": "DONE", "time": "2020-09-15T12:57:39.737Z"} ' await client.handle_message(json.loads(self_trade_prevented)) assert len(client.state.open_orders_by_order_id) == 0 # Only for market orders => when not enough funds have been locked the order fails insufficient_funds = '{"status": "INSUFFICIENT_FUNDS","instrument_code": "ETH_EUR","order_id": ' \ '"fb2d540e-6fe4-411c-8d91-7c94b94d1ae2","remaining": "1.0","channel_name": "TRADING",' \ '"type": "DONE","time": "2020-09-15T13:15:07.214Z"}' await client.handle_message(json.loads(insufficient_funds)) assert len(client.state.open_orders_by_order_id) == 0 # Only for market orders => when not enough liquidity is in the order book the market order fails insufficient_liquidity = '{"status": "INSUFFICIENT_LIQUIDITY","instrument_code": "ETH_EUR","order_id": ' \ '"80cf3fed-5766-4b5c-a378-213c5541bf5d","remaining": "80.0","channel_name": "TRADING",' \ '"type": "DONE","time": "2020-09-15T12:58:12.305Z"}' await client.handle_message(json.loads(insufficient_liquidity)) assert len(client.state.open_orders_by_order_id) == 0 # Internal system error => order was never booked time_to_market_exceeded = '{"status": "TIME_TO_MARKET_EXCEEDED","instrument_code": "ETH_EUR","order_id": ' \ '"bbf4780c-c26b-45dd-a17d-4d554a5b6e30","remaining": "34.0","channel_name": "TRADING",' \ '"type": "DONE","time": "2020-09-15T12:58:12.305Z"}' await client.handle_message(json.loads(time_to_market_exceeded)) assert len(client.state.open_orders_by_order_id) == 0
async def test_message_handling_of_trading_channel_events(): """Tests handling of messages of the trading channel""" async def handle_message(json_message): LOG.debug("ignored message %s", json_message) client = AdvancedBitpandaProWebsocketClient(None, 'test', handle_message) client.apply_trading_buffer() order_creation = '{"order":{"time_in_force":"GOOD_TILL_CANCELLED","is_post_only":false,' \ '"order_id":"c241b172-ee8d-4e1b-8900-6512c2c23579",' \ '"account_id":"379a12c0-4560-11e9-82fe-2b25c6f7d123","instrument_code":"BTC_EUR",' \ '"time":"2020-06-02T06:48:08.278Z","side":"BUY","price":"6000.0","amount":"1.0",' \ '"filled_amount":"0.0","type":"LIMIT"},"channel_name":"ORDERS","type":"ORDER_CREATED",' \ '"time":"2020-06-02T06:48:08.278Z"}' await client.handle_message(json.loads(order_creation)) assert len(client.state.open_orders_by_order_id) == 1 order_booked = '{"order_book_sequence": 1, "instrument_code": "BTC_EUR", "order_id": ' \ '"c241b172-ee8d-4e1b-8900-6512c2c23579", "remaining": "1.0", "channel_name": "TRADING", ' \ '"type": "BOOKED", "time": "2020-06-02T06:48:08.279Z"}' await client.handle_message(json.loads(order_booked)) assert len(client.state.open_orders_by_order_id) == 1 order_filled = '{"order_book_sequence": 1, "side": "BUY", "amount": "1.0", "trade_id": ' \ '"c2591a26-5f76-401f-83ec-4d20657f2db3", "matched_as": "MAKER", "matched_amount": "0.1", ' \ '"matched_price": "6000.0", "instrument_code": "BTC_EUR", "order_id": ' \ '"c241b172-ee8d-4e1b-8900-6512c2c23579", "remaining": "0.9", "channel_name": "TRADING", ' \ '"type": "FILL", "time": "2020-06-02T06:49:08.279Z"}' await client.handle_message(json.loads(order_filled)) assert len(client.state.open_orders_by_order_id) == 1 order_filled_fully = '{"status": "FILLED_FULLY", "order_book_sequence": 1, "instrument_code": "BTC_EUR", ' \ '"order_id": "c241b172-ee8d-4e1b-8900-6512c2c23579", "remaining": "0.0", "channel_name": ' \ '"TRADING", "type": "DONE", "time": "2020-06-02T06:50:08.279Z"}' await client.handle_message(json.loads(order_filled_fully)) assert len(client.state.open_orders_by_order_id) == 0
async def test_handle_trade_settled_updates(): """Test trade settlement events""" client = AdvancedBitpandaProWebsocketClient("irrelevant", "irrelevant", log_messages) await client.handle_message(account_balances_json) balance = client.state.balances["BTC"] assert balance.available == Decimal("8975.828764802") assert balance.locked == Decimal("0.4") # ------- Order created ---------- await client.handle_message(order_created_json) expected_order = client.state.open_orders_by_order_id.get( "65ecb524-4a7f-4b22-aa44-ec0b38d3db9c") # Orders are handled through orders/trading channel assert expected_order is None # On order channel update the order is in the store await client.handle_message(order_created_orders_channel_json) expected_order = client.state.open_orders_by_order_id.get( "65ecb524-4a7f-4b22-aa44-ec0b38d3db9c") assert expected_order is not None assert expected_order.remaining == Decimal("1.0") assert expected_order.order_id == "65ecb524-4a7f-4b22-aa44-ec0b38d3db9c" assert expected_order.price == "8500.0" # check balance balance = client.state.balances["BTC"] assert balance.available == Decimal("8974.828764802") assert balance.locked == Decimal("1.4") # ------- half of order settled ---------- await client.handle_message(trade_settled_partially_json) # check balance again, partially filled balance = client.state.balances["BTC"] assert balance.available == Decimal("8974.828764802") assert balance.locked == Decimal("0.9") # order is still part of open orders expected_order = client.state.open_orders_by_order_id.get( "65ecb524-4a7f-4b22-aa44-ec0b38d3db9c") assert expected_order is not None assert expected_order.remaining == Decimal("1.0") assert expected_order.order_id == "65ecb524-4a7f-4b22-aa44-ec0b38d3db9c" assert expected_order.price == "8500.0" # ------- fully settled order ---------- await client.handle_message(trade_settled_json) # check balance again, not locked anymore balance = client.state.balances["BTC"] assert balance.available == Decimal("8974.828764802") assert balance.locked == Decimal("0.4") # order is completed, on update from trading channel the store is updated client.apply_trading_buffer() await client.handle_message(trade_settled_order_done_json) expected_order = client.state.open_orders_by_order_id.get( "65ecb524-4a7f-4b22-aa44-ec0b38d3db9c") assert expected_order is None inactive_order = client.state.inactive_orders.get( "65ecb524-4a7f-4b22-aa44-ec0b38d3db9c") assert inactive_order is not None assert inactive_order.order_id == "65ecb524-4a7f-4b22-aa44-ec0b38d3db9c" assert inactive_order.remaining == Decimal("0.0") assert inactive_order.order_id == "65ecb524-4a7f-4b22-aa44-ec0b38d3db9c" assert inactive_order.price == "8500.0"