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_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
Exemple #4
0
async def test_handle_order_created_and_then_close():
    """ Test handling of created order 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")

    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

    # check balance
    balance = client.state.balances["BTC"]
    assert balance.available == Decimal("8974.828764802")
    assert balance.locked == Decimal("1.4")

    await client.handle_message(order_closed_json)

    order = client.state.open_orders_by_order_id.get(
        "65ecb524-4a7f-4b22-aa44-ec0b38d3db9c")
    assert order is None
    inactive_order = client.state.inactive_orders.get(
        "65ecb524-4a7f-4b22-aa44-ec0b38d3db9c")
    # Inactive Orders are handled through orders/trading channel
    assert inactive_order is None

    # check balance
    balance = client.state.balances["BTC"]
    assert balance.available == Decimal("8975.828764802")
    assert balance.locked == Decimal("0.4")
Exemple #5
0
async def test_handle_account_balances():
    """Test account balance snapshot handling"""
    client = AdvancedBitpandaProWebsocketClient("irrelevant", "irrelevant",
                                                log_messages)
    await client.handle_message(account_balances_json)
    balance = client.state.balances["EUR"]
    assert balance.available == Decimal("6606076.62363137")
async def test_verify_successful_trading_subscription(event_loop):
    """Handle authenticate messages"""
    api_token = os.environ['BP_PRO_API_TOKEN']
    test_host = os.environ['TEST_HOST']
    future_subscribe = event_loop.create_future()
    future_unsubscribe = event_loop.create_future()

    async def handle_message(json_message):
        LOG.debug("emitted event %s", json_message)
        if json_message["type"] == "SUBSCRIPTIONS":
            LOG.debug("Subscribed to: %s", json_message["channels"][0]["name"])
            if "TRADING" in json_message["channels"][0]["name"]:
                LOG.debug("Subscribed to trading channel")
                future_subscribe.set_result("Success")
        elif json_message["type"] == "UNSUBSCRIBED" and json_message[
                "channel_name"] == "TRADING":
            future_unsubscribe.set_result("Success")
        else:
            LOG.debug("Ignored Message")

    client = AdvancedBitpandaProWebsocketClient(api_token, test_host,
                                                handle_message)
    subscription = TradingSubscription()
    await client.start(Subscriptions([subscription]))
    LOG.info(await future_subscribe)
    await client.unsubscribe(Unsubscription([ChannelName.trading.value]))
    LOG.info(await future_unsubscribe)
    await client.close()
Exemple #7
0
async def test_handle_inactive_orders_snapshot():
    """Test handling of inactive orders snapshot"""
    client = AdvancedBitpandaProWebsocketClient("irrelevant", "irrelevant",
                                                log_messages)
    await client.handle_message(inactive_orders_snapshot_json)
    inactive_orders = client.state.last_24h_inactive_orders
    assert len(inactive_orders) == 4, "expected 4 orders"
    order = inactive_orders.get("297bd6d8-ae68-4547-b414-0bfc87d13019")
    assert order.instrument_code == "BTC_EUR"
    assert order.filled_amount == Decimal("0.2")
Exemple #8
0
async def test_handle_active_orders_snapshot_multiple_instruments():
    """Test active orders snapshot handling"""
    client = AdvancedBitpandaProWebsocketClient("irrelevant", "irrelevant",
                                                log_messages)
    await client.handle_message(
        active_orders_snapshot_multiple_instruments_json)
    open_orders = client.state.open_orders_by_order_id
    assert len(open_orders) == 3
    btc_eur_order = open_orders.get("ce246752-18c9-41a1-872e-759a0016b9c3")
    assert btc_eur_order.instrument_code == "BTC_EUR"
    eth_eur_order = open_orders.get("94cd6c5a-5ab8-4678-b932-7f81083d1f08")
    assert eth_eur_order.instrument_code == "ETH_EUR"
async def main():
    when_msg_received = asyncio.get_event_loop().create_future()

    async def handle_message(event: json):
        LOG.info("%s", event)
        if event["type"] == "ORDER_BOOK_SNAPSHOT":
            when_msg_received.set_result("snapshot received...")

    bp_client = AdvancedBitpandaProWebsocketClient(
        api_token=None,
        wss_host="wss://streams.exchange.bitpanda.com",
        callback=handle_message
    )

    order_book_subscription = OrderBookSubscription(["BTC_EUR"])
    # Order book subscription without ACCOUNT_HISTORY, TRADING & ORDERS channel
    await bp_client.start_with(Subscriptions([order_book_subscription]), False)

    await when_msg_received
    LOG.info("asks book BTC_EUR: %s", bp_client.get_order_book("BTC_EUR").asks)
    LOG.info("bids BTC_EUR: %s", bp_client.get_order_book("BTC_EUR").bids)
    await bp_client.close()
async def main():
    when_order_created = asyncio.get_event_loop().create_future()
    when_order_cancelled = asyncio.get_event_loop().create_future()
    when_order_book_snapshot_received = asyncio.get_event_loop().create_future(
    )

    async def handle_message(event: json):
        LOG.info("%s", event)
        if event["type"] == "ORDER_BOOK_SNAPSHOT":
            when_order_book_snapshot_received.set_result(
                "snapshot received...")
        elif event["type"] == "ORDER_CREATED":
            when_order_created.set_result("created...")
        elif event["type"] == "ORDER_SUBMITTED_FOR_CANCELLATION":
            when_order_cancelled.set_result("cancelled...")

    # add your api token
    my_api_token = "eyJ..."

    bp_client = AdvancedBitpandaProWebsocketClient(
        api_token=my_api_token,
        wss_host="wss://streams.exchange.bitpanda.com",
        callback=handle_message)

    account_history_subscription = AccountHistorySubscription()
    trading_subscription = TradingSubscription()
    orders_subscription = OrdersSubscription()
    order_book_subscription = OrderBookSubscription(["BTC_EUR"])
    await bp_client.start(
        Subscriptions([
            account_history_subscription, orders_subscription,
            trading_subscription, order_book_subscription
        ]))

    await when_order_book_snapshot_received
    LOG.info("asks book BTC_EUR: %s", bp_client.get_order_book("BTC_EUR").asks)
    LOG.info("bids BTC_EUR: %s", bp_client.get_order_book("BTC_EUR").bids)

    client_id = str(uuid.uuid4())
    new_order_with_client_id = CreateOrder(
        LimitOrder("BTC_EUR", Side.buy, 0.01, 1000.50, client_id))
    LOG.info("Creating new Order with client_id: %s", new_order_with_client_id)
    await bp_client.create_order(new_order_with_client_id)
    await when_order_created
    LOG.info("Balances: %s", bp_client.get_state().balances)
    LOG.info("Open orders: %s", bp_client.get_state().open_orders_by_order_id)

    LOG.info("Cancel Order with client_id: %s", client_id)
    await bp_client.cancel_order(CancelOrderByClientId(client_id))
    await when_order_cancelled
    LOG.info("Open orders: %s", bp_client.get_state().open_orders_by_order_id)

    await bp_client.close()
Exemple #11
0
async def main():
    when_order_created = asyncio.get_event_loop().create_future()
    when_order_cancelled = asyncio.get_event_loop().create_future()

    async def handle_message(event: json):
        LOG.info("%s", event)
        if event["type"] == "ORDER_CREATED":
            when_order_created.set_result("created...")
        elif event["type"] == "ORDER_SUBMITTED_FOR_CANCELLATION":
            when_order_cancelled.set_result("cancelled...")

    # add your api token
    my_api_token = "eyJ..."

    bp_client = AdvancedBitpandaProWebsocketClient(
        api_token=my_api_token,
        wss_host="wss://streams.exchange.bitpanda.com",
        callback=handle_message)

    # Activates ACCOUNT_HISTORY, TRADING & ORDERS channel
    await bp_client.start_with(None, True)

    client_id = str(uuid.uuid4())
    new_order_with_client_id = CreateOrder(
        LimitOrder("BTC_EUR", Side.buy, Decimal('0.01'), Decimal('1000.50'),
                   client_id))
    LOG.info("Creating new Order with client_id: %s", new_order_with_client_id)
    await bp_client.create_order(new_order_with_client_id)
    await when_order_created
    LOG.info("Balances: %s", bp_client.get_state().balances)
    LOG.info("Open orders: %s", bp_client.get_state().open_orders_by_order_id)

    LOG.info("Cancel Order with client_id: %s", client_id)
    await bp_client.cancel_order(CancelOrderByClientId(client_id))
    await when_order_cancelled
    LOG.info("Open orders: %s", bp_client.get_state().open_orders_by_order_id)

    await bp_client.close()
Exemple #12
0
async def test_handle_active_orders_snapshot():
    """Test active orders snapshot handling"""
    client = AdvancedBitpandaProWebsocketClient("irrelevant", "irrelevant",
                                                log_messages)
    await client.handle_message(active_orders_snapshot_json)
    open_orders = client.state.open_orders_by_order_id
    assert len(open_orders) == 1, "expected 1 order"
    order = open_orders.get("6894fe05-4071-49ca-813e-d88d3621e168")
    assert order.instrument_code == "BTC_EUR"
    assert order.order_id == "6894fe05-4071-49ca-813e-d88d3621e168"
    assert order.type == "LIMIT"
    assert order.time_in_force == "GOOD_TILL_CANCELLED"
    assert order.side == "SELL"
    assert order.price == Decimal("18500.0")
    assert order.remaining == Decimal("0.1")
    assert order.client_id == "082e0b7c-1888-4db2-b53e-208b64ae09b3"
Exemple #13
0
async def test_verify_handling_of_order_books():
    """test that the client handles order book messages correctly"""
    async def log_messages(json_message):
        """Callback only logging messages"""
        LOG.debug("message: %s", json_message)

    client = AdvancedBitpandaProWebsocketClient(None, 'test', log_messages)
    subscription = '{"channels":[{"instrument_codes":["BTC_EUR","ETH_EUR"],"depth":200,"name":"ORDER_BOOK"}],' \
                   '"type":"SUBSCRIPTIONS","time":"2020-07-15T12:00:00.364Z"}'
    await client.handle_message(json.loads(subscription))
    empty_oder_book_btc_eur = client.get_order_book('BTC_EUR')
    assert bool(empty_oder_book_btc_eur.get_asks()
                ) is False, "expected empty order book for btc_eur"
    assert bool(empty_oder_book_btc_eur.get_bids()
                ) is False, "expected empty order book for btc_eur"
    empty_oder_book_eth_eur = client.get_order_book('ETH_EUR')
    assert bool(empty_oder_book_eth_eur.get_asks()
                ) is False, "expected empty order book for eth_eur"
    assert bool(empty_oder_book_eth_eur.get_bids()
                ) is False, "expected empty order book for eth_eur"
    raw_snapshot_btc_eur = '{"instrument_code":"BTC_EUR","bids":[["8860.92","0.43712"],["8858.75","0.03225"],' \
                           '["8856.0","0.15857"],["8855.0","0.45334"],["8852.11","0.0216"],["8850.0","0.60744"],' \
                           '["8845.01","3.45043"],["8845.0","3.52483"],["8838.77","0.51727"],["8835.0","0.00991"]],' \
                           '"asks":[["8874.23","0.36287"],["8876.4","0.0123"],["8883.0","0.43531"],["8884.0",' \
                           '"1.11066"],["8885.99","2.27369"],["8886.0","0.08116"],["8887.0","0.30046"],["8888.0",' \
                           '"0.44740"],["8890.0","0.993"],["8896.42","0.42775"]],"channel_name":"ORDER_BOOK",' \
                           '"type":"ORDER_BOOK_SNAPSHOT","time":"2020-07-15T12:00:00.365Z"}'
    await client.handle_message(json.loads(raw_snapshot_btc_eur))
    oder_book_btc_eur = client.get_order_book('BTC_EUR')
    assert bool(
        oder_book_btc_eur.get_asks()) is True, "expected asks for btc_eur"
    assert bool(
        oder_book_btc_eur.get_bids()) is True, "expected bids for btc_eur"
    raw_snapshot_eth_eur = '{"instrument_code":"ETH_EUR","bids":[["186.3","20.4"]],' \
                           '"asks":[["186.58","0.36287"]],"channel_name":"ORDER_BOOK",' \
                           '"type":"ORDER_BOOK_SNAPSHOT","time":"2020-07-15T12:00:00.366Z"}'
    await client.handle_message(json.loads(raw_snapshot_eth_eur))
    oder_book_eth_eur = client.get_order_book('ETH_EUR')
    assert bool(
        oder_book_eth_eur.get_asks()) is True, "expected asks for eth_eur"
    assert bool(
        oder_book_eth_eur.get_bids()) is True, "expected bids for eth_eur"
    unsubscribed = '{"channel_name":"ORDER_BOOK","type":"UNSUBSCRIBED","time":"2020-07-15T12:30:00.288Z"}'
    await client.handle_message(json.loads(unsubscribed))
    assert bool(client.get_order_books()) is False
Exemple #14
0
async def test_handle_out_of_order_sequenced_message():
    """Test situations when an event arrives with an older sequence"""
    client = AdvancedBitpandaProWebsocketClient("irrelevant", "irrelevant",
                                                log_messages)
    await client.handle_message(account_balances_json)

    await client.handle_message(order_created_json)
    balance = client.state.balances["BTC"]
    assert balance.available == Decimal("8974.828764802")
    assert balance.locked == Decimal("1.4")
    # an event with older sequence should be ignored, therefore no change in the balance
    await client.handle_message(old_seq_order_created_json)
    balance = client.state.balances["BTC"]
    assert balance.available == Decimal("8974.828764802")
    assert balance.locked == Decimal("1.4")
    # a newer event with higher sequence should be accepted
    await client.handle_message(newer_seq_order_created_json)
    balance = client.state.balances["BTC"]
    assert balance.available == Decimal("8569.228764802")
    assert balance.locked == Decimal("2.2")
async def test_message_handling_of_orders_channel_by_using_order_id():
    """Tests handling of messages of the orders channel"""
    async def handle_message(json_message):
        LOG.debug("ignored message %s", json_message)

    client = AdvancedBitpandaProWebsocketClient(None, 'test', handle_message)
    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_client_id) == 0
    assert len(client.state.open_orders_by_order_id) == 1
    order_cancellation = '{"order_id":"c241b172-ee8d-4e1b-8900-6512c2c23579","channel_name":"ORDERS",' \
                         '"type":"ORDER_SUBMITTED_FOR_CANCELLATION","time":"2020-06-02T06:48:08.381Z"} '
    await client.handle_message(json.loads(order_cancellation))
    assert len(client.state.open_orders_by_client_id) == 0
    # Open order is removed from store on trading channel update
    assert len(client.state.open_orders_by_order_id) == 1
Exemple #16
0
async def test_withdrawal_of_funds():
    """Verify correct balance after withdrawal"""
    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")

    balance = client.state.balances["EUR"]
    assert balance.available == Decimal("6606076.62363137")
    assert balance.locked == Decimal("0.0")

    # Change in BTC balance after 0.22 BTC withdrawal
    await client.handle_message(account_balance_withdrawal)
    balance = client.state.balances["BTC"]
    assert balance.available == Decimal("8975.608764802")
    assert balance.locked == Decimal("0.12")

    # No change in EUR balance
    balance = client.state.balances["EUR"]
    assert balance.available == Decimal("6606076.62363137")
    assert balance.locked == Decimal("0.0")
Exemple #17
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"