gtat-tech-career-kickstarte.../solution/tests/test_order_book_system.py

1167 lines
62 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

from decimal import Decimal
import logging
from unittest.mock import ANY
import pytest
from connection.tcp_connection_manager import TcpConnectionManager
from proto.common_pb2 import Instrument, Side
from tests.common.mock_expectations import CallExpectationsManager
from tests.test_client.admin_test_client import AdminClientConnectionHandlerFactory, AdminTestClient
from tests.test_client.order_book_test_client import (
OrderBookClientConnectionHandlerFactory, OrderBookCreatedExpectation, OrderBookTestClient)
logger = logging.getLogger(__name__)
class TestOrderBookSystem:
PROTOCOL = "order_book"
@pytest.fixture(autouse=True)
def setup(self, request: pytest.FixtureRequest) -> None:
self.test_name = request.node.name
logger.info(f"Setting up test: {self.test_name}")
self.tcp_connection_manager = TcpConnectionManager()
self.call_expectations_manager = CallExpectationsManager()
self.call_expectations_manager.setup_network(self.tcp_connection_manager)
self.order_book_client_factory = OrderBookClientConnectionHandlerFactory(self.call_expectations_manager)
self._admin_factory = AdminClientConnectionHandlerFactory(self.call_expectations_manager)
self._admin_client: AdminTestClient | None = None
self._next_instrument_id = 1
def _connect_unauthenticated(self) -> OrderBookTestClient:
return self.tcp_connection_manager.connect(self.server_address, self.order_book_client_factory)
def _get_admin_client(self) -> AdminTestClient:
if self._admin_client is None:
admin_address = self.orchestrator.get_server_address("admin")
self._admin_client = self.tcp_connection_manager.connect(admin_address, self._admin_factory)
return self._admin_client
def _create_order_book_via_admin(self, order_book_client: OrderBookTestClient,
tick_size: Decimal) -> OrderBookCreatedExpectation:
on_created = order_book_client.expect_on_order_book_created_event(tick_size=tick_size)
instrument = Instrument(
symbol=f"TEST.{self._next_instrument_id}",
description="Test instrument",
currency="USD",
multiplier=1.0)
self._next_instrument_id += 1
admin_client = self._get_admin_client()
admin_client.test_create_instrument(instrument, tick_size)
assert on_created.fulfilled, "OnOrderBookCreated not received"
order_book_client._last_order_book_id = on_created.get_message().order_book_id
return on_created
def test_create_order_book(self) -> None:
client = self._connect_unauthenticated()
self._create_order_book_via_admin(client, tick_size=Decimal("0.01"))
self.call_expectations_manager.verify_expectations()
self.call_expectations_manager.verify_no_unexpected_calls()
def test_insert_order(self) -> None:
client = self._connect_unauthenticated()
self._create_order_book_via_admin(client, tick_size=Decimal("0.01"))
client.test_insert_order(side=Side.BUY, price=Decimal("100.0"), quantity=10, username=self.test_name)
self.call_expectations_manager.verify_expectations()
self.call_expectations_manager.verify_no_unexpected_calls()
def test_cancel_order(self) -> None:
client = self._connect_unauthenticated()
self._create_order_book_via_admin(client, tick_size=Decimal("0.01"))
client.test_insert_order(side=Side.BUY, price=Decimal("100.0"), quantity=10, username=self.test_name)
client.test_cancel_order()
self.call_expectations_manager.verify_expectations()
self.call_expectations_manager.verify_no_unexpected_calls()
@pytest.mark.parametrize(
"buy_price, buy_quantity, sell_price, sell_quantity, aggressor_side, expected_trade_price, expected_trade_quantity",
[
pytest.param(Decimal("100.0"), 10, Decimal("100.0"), 10, Side.BUY, Decimal("100.0"), 10, id="same_price_same_quantity_buyer_hit"),
pytest.param(Decimal("100.0"), 10, Decimal("100.0"), 10, Side.SELL, Decimal("100.0"), 10, id="same_price_same_quantity_seller_hit"),
pytest.param(Decimal("100.0"), 10, Decimal("100.0"), 5, Side.BUY, Decimal("100.0"), 5, id="same_price_different_quantity_buyer_hit"),
pytest.param(Decimal("100.0"), 10, Decimal("100.0"), 5, Side.SELL, Decimal("100.0"), 5, id="same_price_different_quantity_seller_hit"),
pytest.param(Decimal("100.0"), 10, Decimal("99.99"), 10, Side.BUY, Decimal("99.99"), 10, id="different_price_same_quantity_buyer_hit"),
pytest.param(Decimal("100.0"), 10, Decimal("99.99"), 10, Side.SELL, Decimal("100.0"), 10, id="different_price_same_quantity_seller_hit"),
pytest.param(Decimal("100.0"), 10, Decimal("99.99"), 5, Side.BUY, Decimal("99.99"), 5, id="different_price_different_quantity_buyer_hit"),
pytest.param(Decimal("100.0"), 10, Decimal("99.99"), 5, Side.SELL, Decimal("100.0"), 5, id="different_price_different_quantity_seller_hit"),
]
)
def test_match_single_order(
self, buy_price: Decimal, buy_quantity: int, sell_price: Decimal, sell_quantity: int, aggressor_side: Side.ValueType,
expected_trade_price: Decimal, expected_trade_quantity: int) -> None:
if aggressor_side == Side.BUY:
aggressive_price = buy_price
aggressive_quantity = buy_quantity
passive_price = sell_price
passive_quantity = sell_quantity
else:
aggressive_price = sell_price
aggressive_quantity = sell_quantity
passive_price = buy_price
passive_quantity = buy_quantity
logger.info("Connecting first client and creating order book")
self.client_passive = self._connect_unauthenticated()
on_created = self._create_order_book_via_admin(self.client_passive, tick_size=Decimal("0.01"))
order_book_id = on_created.get_message().order_book_id
logger.info("Inserting passive order with first client")
passive_side = Side.BUY if aggressor_side == Side.SELL else Side.SELL
passive_order_response_expectation = self.client_passive.test_insert_order(
side=passive_side, price=passive_price, quantity=passive_quantity, order_book_id=order_book_id,
username=f"{self.test_name}_1")
passive_order_id = passive_order_response_expectation.get_response().order_id
logger.info("Connecting second client and expecting existing orders to be published")
self.client_aggressor = self._connect_unauthenticated()
self.client_aggressor.expect_on_order_book_created_event(
tick_size=Decimal("0.01"), order_book_id=order_book_id)
self.client_aggressor.expect_on_order_inserted_event(
order_book_id=order_book_id, order_id=passive_order_id, side=passive_side, price=passive_price, quantity=passive_quantity)
# Verify that the second client received the full state of the order book
self.call_expectations_manager.verify_expectations()
logger.info("Inserting aggressive order with second client and expecting trade to be published")
aggressive_order_response_expectation = self.client_aggressor.test_insert_order(
side=aggressor_side, price=aggressive_price, quantity=aggressive_quantity, order_book_id=order_book_id,
username=f"{self.test_name}_2")
aggressive_order_response = aggressive_order_response_expectation.get_response()
aggressive_order_id = aggressive_order_response.order_id
assert len(aggressive_order_response.trade_ids) == 1
trade_id = aggressive_order_response.trade_ids[0]
assert aggressive_order_response.traded_quantity == expected_trade_quantity
# expect order from client2 to be published to client1
self.client_passive.expect_on_order_inserted_event(
order_book_id=order_book_id, order_id=aggressive_order_id, side=aggressor_side, price=aggressive_price,
quantity=aggressive_quantity)
# expect trade to be published to both clients
buy_order_id = passive_order_id if passive_side == Side.BUY else aggressive_order_id
sell_order_id = aggressive_order_id if passive_side == Side.BUY else passive_order_id
self.client_passive.expect_on_trade_event(
trade_id=trade_id, order_book_id=order_book_id, buy_order_id=buy_order_id, sell_order_id=sell_order_id,
aggressive_side=aggressor_side, price=expected_trade_price, quantity=expected_trade_quantity)
self.client_aggressor.expect_on_trade_event(
trade_id=trade_id, order_book_id=order_book_id, buy_order_id=buy_order_id, sell_order_id=sell_order_id,
aggressive_side=aggressor_side, price=expected_trade_price, quantity=expected_trade_quantity)
self.call_expectations_manager.verify_expectations()
self.call_expectations_manager.verify_no_unexpected_calls()
def test_order_cancellation_after_partial_fill(self) -> None:
logger.info("Start matching orders with partial fill")
self.test_match_single_order(
buy_price=Decimal("100.0"), buy_quantity=10,
sell_price=Decimal("100.0"), sell_quantity=5,
aggressor_side=Side.BUY,
expected_trade_price=Decimal("100.0"), expected_trade_quantity=5)
assert self.client_aggressor._last_order_id is not None
order_id = self.client_aggressor._last_order_id
order_book_id = self.client_passive._last_order_book_id
self.client_aggressor.test_cancel_order(order_book_id=order_book_id)
self.client_passive.expect_on_order_cancelled_event(order_id=order_id)
self.call_expectations_manager.verify_expectations()
def test_order_cancellation_after_full_fill(self) -> None:
logger.info("Start matching orders fully")
self.test_match_single_order(
buy_price=Decimal("100.0"), buy_quantity=10,
sell_price=Decimal("100.0"), sell_quantity=10,
aggressor_side=Side.BUY,
expected_trade_price=Decimal("100.0"), expected_trade_quantity=10)
order_book_id = self.client_passive._last_order_book_id
self.client_aggressor.test_cancel_order(order_book_id=order_book_id, expect_success=False)
self.client_passive.test_cancel_order(order_book_id=order_book_id, expect_success=False)
self.call_expectations_manager.verify_expectations()
self.call_expectations_manager.verify_no_unexpected_calls()
@pytest.mark.parametrize(
"num_levels, orders_per_level",
[
pytest.param(5, 1, id="single_order_per_level"),
pytest.param(1, 5, id="single_level_multiple_orders"),
pytest.param(5, 5, id="multiple_levels_multiple_orders"),
pytest.param(50, 5, id="lots_of_levels_multiple_orders"),
pytest.param(50, 20, id="lots_of_levels_lots_of_orders"),
]
)
def test_match_single_aggressive_order_on_multiple_levels(self, num_levels: int, orders_per_level: int) -> None:
tick_size = Decimal("0.01")
best_bid = Decimal("100.0")
bid_quantity = 10
logger.info("Connecting first client and creating order book")
self.client_passive = self._connect_unauthenticated()
on_created = self._create_order_book_via_admin(self.client_passive, tick_size=tick_size)
order_book_id = on_created.get_message().order_book_id
logger.info("Inserting passive orders with first client")
logger.info(f"Inserting {orders_per_level} passive orders per level, at {num_levels} levels")
passive_order_response_expectations = []
bid_prices = [best_bid - i * tick_size for i in range(num_levels)]
for price in bid_prices:
for _ in range(orders_per_level):
passive_order_response_expectations.append(self.client_passive.test_insert_order(
side=Side.BUY, price=price, quantity=bid_quantity, order_book_id=order_book_id,
username=f"{self.test_name}_1"))
logger.info("Connecting second client and expecting existing orders to be published")
self.client_aggressor = self._connect_unauthenticated()
self.client_aggressor.expect_on_order_book_created_event(
tick_size=tick_size, order_book_id=order_book_id)
buy_order_ids = []
for passive_order_response_expectation in passive_order_response_expectations:
passive_order_response = passive_order_response_expectation.get_response()
buy_order_ids.append(passive_order_response.order_id)
self.client_aggressor.expect_on_order_inserted_event(
order_book_id=order_book_id, order_id=passive_order_response.order_id, side=Side.BUY,
price=ANY, quantity=bid_quantity)
# Verify that the second client received the full state of the order book
self.call_expectations_manager.verify_expectations()
worst_bid = min(bid_prices)
total_bid_quantity = num_levels * orders_per_level * bid_quantity
logger.info("Inserting aggressive order with second client and expecting to trade all levels")
aggressive_order_response_expectation = self.client_aggressor.test_insert_order(
side=Side.SELL, price=worst_bid, quantity=total_bid_quantity, order_book_id=order_book_id,
username=f"{self.test_name}_2")
aggressive_order_response = aggressive_order_response_expectation.get_response()
sell_order_id = aggressive_order_response.order_id
assert len(aggressive_order_response.trade_ids) == num_levels * orders_per_level
aggressive_order_response_trade_ids = set(aggressive_order_response.trade_ids)
# expect order from client2 to be published (also) to client1
self.client_passive.expect_on_order_inserted_event(
order_book_id=order_book_id, order_id=ANY, side=Side.SELL, price=worst_bid, quantity=total_bid_quantity)
# expect trades to be published to both clients
on_trade_expectations = []
for price in bid_prices:
for _ in range(orders_per_level):
on_trade_expectations.append(self.client_passive.expect_on_trade_event(
trade_id=ANY, order_book_id=order_book_id, buy_order_id=ANY, sell_order_id=sell_order_id,
aggressive_side=Side.SELL, price=price, quantity=bid_quantity))
on_trade_expectations.append(self.client_aggressor.expect_on_trade_event(
trade_id=ANY, order_book_id=order_book_id, buy_order_id=ANY, sell_order_id=sell_order_id,
aggressive_side=Side.SELL, price=price, quantity=bid_quantity))
self.call_expectations_manager.verify_expectations()
self.call_expectations_manager.verify_no_unexpected_calls()
# verify that all trade ids from the aggressive order response are present in the trade events
trade_ids = set(trade_expectation.get_message().trade_id for trade_expectation in on_trade_expectations)
assert trade_ids == aggressive_order_response_trade_ids
# =========================================================================
# State publishing to new clients
# =========================================================================
def test_new_client_receives_existing_order_book(self) -> None:
"""A client connecting after an order book is created should receive OnOrderBookCreated."""
client1 = self._connect_unauthenticated()
on_created = self._create_order_book_via_admin(client1, tick_size=Decimal("0.01"))
order_book_id = on_created.get_message().order_book_id
client2 = self._connect_unauthenticated()
client2.expect_on_order_book_created_event(tick_size=Decimal("0.01"), order_book_id=order_book_id)
self.call_expectations_manager.verify_expectations()
self.call_expectations_manager.verify_no_unexpected_calls()
def test_new_client_receives_existing_orders(self) -> None:
"""A client connecting after orders are placed should receive OnOrderInserted for each resting order."""
client1 = self._connect_unauthenticated()
on_created = self._create_order_book_via_admin(client1, tick_size=Decimal("0.01"))
order_book_id = on_created.get_message().order_book_id
buy_resp = client1.test_insert_order(
side=Side.BUY, price=Decimal("100.0"), quantity=10,
order_book_id=order_book_id, username=f"{self.test_name}_1")
buy_order_id = buy_resp.get_response().order_id
sell_resp = client1.test_insert_order(
side=Side.SELL, price=Decimal("101.0"), quantity=5,
order_book_id=order_book_id, username=f"{self.test_name}_1")
sell_order_id = sell_resp.get_response().order_id
client2 = self._connect_unauthenticated()
client2.expect_on_order_book_created_event(tick_size=Decimal("0.01"), order_book_id=order_book_id)
client2.expect_on_order_inserted_event(
order_book_id=order_book_id, order_id=buy_order_id,
side=Side.BUY, price=Decimal("100.0"), quantity=10)
client2.expect_on_order_inserted_event(
order_book_id=order_book_id, order_id=sell_order_id,
side=Side.SELL, price=Decimal("101.0"), quantity=5)
self.call_expectations_manager.verify_expectations()
self.call_expectations_manager.verify_no_unexpected_calls()
def test_new_client_receives_multiple_order_books(self) -> None:
"""A client connecting should receive OnOrderBookCreated for all existing order books."""
client1 = self._connect_unauthenticated()
on_created_1 = self._create_order_book_via_admin(client1, tick_size=Decimal("0.01"))
ob_id_1 = on_created_1.get_message().order_book_id
on_created_2 = self._create_order_book_via_admin(client1, tick_size=Decimal("0.05"))
ob_id_2 = on_created_2.get_message().order_book_id
client2 = self._connect_unauthenticated()
client2.expect_on_order_book_created_event(tick_size=Decimal("0.01"), order_book_id=ob_id_1)
client2.expect_on_order_book_created_event(tick_size=Decimal("0.05"), order_book_id=ob_id_2)
self.call_expectations_manager.verify_expectations()
self.call_expectations_manager.verify_no_unexpected_calls()
def test_new_client_does_not_receive_cancelled_orders(self) -> None:
"""Cancelled orders must not appear in the state published to a newly connecting client."""
client1 = self._connect_unauthenticated()
on_created = self._create_order_book_via_admin(client1, tick_size=Decimal("0.01"))
order_book_id = on_created.get_message().order_book_id
buy_resp = client1.test_insert_order(
side=Side.BUY, price=Decimal("99.0"), quantity=10,
order_book_id=order_book_id, username=f"{self.test_name}_1")
buy_order_id = buy_resp.get_response().order_id
client1.test_cancel_order(order_id=buy_order_id, order_book_id=order_book_id)
sell_resp = client1.test_insert_order(
side=Side.SELL, price=Decimal("101.0"), quantity=5,
order_book_id=order_book_id, username=f"{self.test_name}_1")
sell_order_id = sell_resp.get_response().order_id
client2 = self._connect_unauthenticated()
client2.expect_on_order_book_created_event(tick_size=Decimal("0.01"), order_book_id=order_book_id)
client2.expect_on_order_inserted_event(
order_book_id=order_book_id, order_id=sell_order_id,
side=Side.SELL, price=Decimal("101.0"), quantity=5)
self.call_expectations_manager.verify_expectations()
self.call_expectations_manager.verify_no_unexpected_calls()
def test_new_client_receives_partially_filled_orders(self) -> None:
"""A partially filled order should still appear in state published to a newly connecting client."""
client1 = self._connect_unauthenticated()
on_created = self._create_order_book_via_admin(client1, tick_size=Decimal("0.01"))
order_book_id = on_created.get_message().order_book_id
buy_resp = client1.test_insert_order(
side=Side.BUY, price=Decimal("100.0"), quantity=10,
order_book_id=order_book_id, username=f"{self.test_name}_buyer")
buy_order_id = buy_resp.get_response().order_id
sell_resp = client1.test_insert_order(
side=Side.SELL, price=Decimal("100.0"), quantity=5,
order_book_id=order_book_id, username=f"{self.test_name}_seller")
sell_response = sell_resp.get_response()
assert sell_response.traded_quantity == 5
trade_id = sell_response.trade_ids[0]
client1.expect_on_trade_event(
trade_id=trade_id, order_book_id=order_book_id,
buy_order_id=buy_order_id, sell_order_id=sell_response.order_id,
aggressive_side=Side.SELL, price=Decimal("100.0"), quantity=5)
self.call_expectations_manager.verify_expectations()
client2 = self._connect_unauthenticated()
client2.expect_on_order_book_created_event(tick_size=Decimal("0.01"), order_book_id=order_book_id)
client2.expect_on_order_inserted_event(
order_book_id=order_book_id, order_id=buy_order_id,
side=Side.BUY, price=Decimal("100.0"), quantity=10)
self.call_expectations_manager.verify_expectations()
self.call_expectations_manager.verify_no_unexpected_calls()
# =========================================================================
# Error handling
# =========================================================================
def test_insert_order_on_nonexistent_order_book(self) -> None:
"""Inserting on an order_book_id that does not exist should return an error response."""
client = self._connect_unauthenticated()
client._last_order_book_id = 99999
client.test_insert_order(
side=Side.BUY, price=Decimal("100.0"), quantity=10,
order_book_id=99999, username=self.test_name,
expect_success=False, expect_public_feed=False)
self.call_expectations_manager.verify_expectations()
self.call_expectations_manager.verify_no_unexpected_calls()
def test_cancel_nonexistent_order(self) -> None:
"""Cancelling an order_id that does not exist should return an error response."""
client = self._connect_unauthenticated()
self._create_order_book_via_admin(client, tick_size=Decimal("0.01"))
client.test_cancel_order(
order_id=99999, expect_success=False, expect_public_feed=False)
self.call_expectations_manager.verify_expectations()
self.call_expectations_manager.verify_no_unexpected_calls()
def test_cancel_same_order_twice(self) -> None:
"""Cancelling the same order a second time should return an error response."""
client = self._connect_unauthenticated()
self._create_order_book_via_admin(client, tick_size=Decimal("0.01"))
resp = client.test_insert_order(
side=Side.BUY, price=Decimal("100.0"), quantity=10, username=self.test_name)
order_id = resp.get_response().order_id
client.test_cancel_order(order_id=order_id)
client.test_cancel_order(
order_id=order_id, expect_success=False, expect_public_feed=False)
self.call_expectations_manager.verify_expectations()
self.call_expectations_manager.verify_no_unexpected_calls()
@pytest.mark.parametrize(
"tick_size, valid_price, invalid_price",
[
pytest.param(Decimal("0.01"), Decimal("100.01"), Decimal("100.005"), id="tick_0.01"),
pytest.param(Decimal("0.05"), Decimal("100.05"), Decimal("100.01"), id="tick_0.05"),
pytest.param(Decimal("0.50"), Decimal("100.50"), Decimal("100.25"), id="tick_0.50"),
pytest.param(Decimal("1.00"), Decimal("100.0"), Decimal("100.50"), id="tick_1.00"),
]
)
def test_insert_order_price_must_be_multiple_of_tick_size(
self, tick_size: Decimal, valid_price: Decimal, invalid_price: Decimal) -> None:
"""Orders with a price that is not a multiple of the tick size should be rejected."""
client = self._connect_unauthenticated()
self._create_order_book_via_admin(client, tick_size=tick_size)
client.test_insert_order(
side=Side.BUY, price=invalid_price, quantity=10,
username=self.test_name,
expect_success=False, expect_public_feed=False)
self.call_expectations_manager.verify_expectations()
self.call_expectations_manager.verify_no_unexpected_calls()
client.test_insert_order(
side=Side.BUY, price=valid_price, quantity=10,
username=self.test_name)
self.call_expectations_manager.verify_expectations()
self.call_expectations_manager.verify_no_unexpected_calls()
# =========================================================================
# Floating-point price precision
# =========================================================================
@pytest.mark.parametrize(
"tick_size, sell_price, buy_price, expected_trade_price",
[
pytest.param(
Decimal("0.01"), Decimal("100.03"), Decimal("100.03"), Decimal("100.03"),
id="0.03_is_not_exact_in_float"),
pytest.param(
Decimal("0.01"), Decimal("100.07"), Decimal("100.07"), Decimal("100.07"),
id="0.07_is_not_exact_in_float"),
pytest.param(
Decimal("0.0001"), Decimal("100.0001"), Decimal("100.0001"), Decimal("100.0001"),
id="4dp_exact_match"),
pytest.param(
Decimal("0.0001"), Decimal("99.9999"), Decimal("99.9999"), Decimal("99.9999"),
id="4dp_just_below_round_number"),
pytest.param(
Decimal("0.0001"), Decimal("100.0003"), Decimal("100.0007"), Decimal("100.0003"),
id="4dp_cross_trades_at_passive_ask"),
pytest.param(
Decimal("0.0001"), Decimal("99.99999"), Decimal("99.99999"), Decimal("99.99999"),
id="5dp_just_below_round_number"),
pytest.param(
Decimal("0.0001"), Decimal("100.00003"), Decimal("100.00007"), Decimal("100.0000"),
id="5dp_crossing_outside_precision"),
pytest.param(
Decimal("0.0001"), Decimal("0.0001"), Decimal("0.0001"), Decimal("0.0001"),
id="smallest_possible_price"),
pytest.param(
Decimal("0.01"), Decimal("0.10"), Decimal("0.10"), Decimal("0.10"),
id="0.1_is_not_exact_in_float"),
pytest.param(
Decimal("0.01"), Decimal("33.33"), Decimal("33.33"), Decimal("33.33"),
id="repeating_decimal_in_float"),
]
)
def test_price_precision_through_float_protocol(
self, tick_size: Decimal, sell_price: Decimal, buy_price: Decimal,
expected_trade_price: Decimal) -> None:
"""Prices use float (double) on the wire. Implementations must not lose precision up to 4 decimal places."""
client_passive = self._connect_unauthenticated()
on_created = self._create_order_book_via_admin(client_passive, tick_size=tick_size)
order_book_id = on_created.get_message().order_book_id
passive_resp = client_passive.test_insert_order(
side=Side.SELL, price=sell_price, quantity=10,
order_book_id=order_book_id, username=f"{self.test_name}_seller")
passive_order_id = passive_resp.get_response().order_id
client_aggressor = self._connect_unauthenticated()
client_aggressor.expect_on_order_book_created_event(
tick_size=tick_size, order_book_id=order_book_id)
client_aggressor.expect_on_order_inserted_event(
order_book_id=order_book_id, order_id=passive_order_id,
side=Side.SELL, price=sell_price, quantity=10)
self.call_expectations_manager.verify_expectations()
client_aggressor._last_order_book_id = order_book_id
agg_resp = client_aggressor.test_insert_order(
side=Side.BUY, price=buy_price, quantity=10,
order_book_id=order_book_id, username=f"{self.test_name}_buyer")
agg_response = agg_resp.get_response()
assert len(agg_response.trade_ids) == 1, \
f"Expected a trade at sell={sell_price} buy={buy_price}, but got none"
assert agg_response.traded_quantity == 10
trade_id = agg_response.trade_ids[0]
agg_order_id = agg_response.order_id
client_passive.expect_on_order_inserted_event(
order_book_id=order_book_id, order_id=agg_order_id,
side=Side.BUY, price=buy_price, quantity=10)
client_passive.expect_on_trade_event(
trade_id=trade_id, order_book_id=order_book_id,
buy_order_id=agg_order_id, sell_order_id=passive_order_id,
aggressive_side=Side.BUY, price=expected_trade_price, quantity=10)
client_aggressor.expect_on_trade_event(
trade_id=trade_id, order_book_id=order_book_id,
buy_order_id=agg_order_id, sell_order_id=passive_order_id,
aggressive_side=Side.BUY, price=expected_trade_price, quantity=10)
self.call_expectations_manager.verify_expectations()
self.call_expectations_manager.verify_no_unexpected_calls()
# =========================================================================
# Response field validation
# =========================================================================
def test_insert_order_response_no_trades_when_no_match(self) -> None:
"""InsertOrderResponse should have empty trade_ids and zero traded_quantity when no match occurs."""
client = self._connect_unauthenticated()
self._create_order_book_via_admin(client, tick_size=Decimal("0.01"))
resp = client.test_insert_order(
side=Side.BUY, price=Decimal("100.0"), quantity=10, username=self.test_name)
response = resp.get_response()
assert len(response.trade_ids) == 0, \
f"Expected no trades, got trade_ids={list(response.trade_ids)}"
assert response.traded_quantity == 0, \
f"Expected traded_quantity=0, got {response.traded_quantity}"
self.call_expectations_manager.verify_expectations()
self.call_expectations_manager.verify_no_unexpected_calls()
def test_cancel_order_response_remaining_quantity(self) -> None:
"""CancelOrderResponse should report the full remaining (unfilled) quantity."""
client = self._connect_unauthenticated()
self._create_order_book_via_admin(client, tick_size=Decimal("0.01"))
resp = client.test_insert_order(
side=Side.BUY, price=Decimal("100.0"), quantity=10, username=self.test_name)
order_id = resp.get_response().order_id
cancel_resp = client.test_cancel_order(order_id=order_id)
cancel_response = cancel_resp.get_response()
assert cancel_response.remaining_quantity == 10, \
f"Expected remaining_quantity=10, got {cancel_response.remaining_quantity}"
self.call_expectations_manager.verify_expectations()
self.call_expectations_manager.verify_no_unexpected_calls()
def test_trade_ids_are_unique_across_matches(self) -> None:
"""Every trade executed must receive a globally unique trade_id."""
client = self._connect_unauthenticated()
self._create_order_book_via_admin(client, tick_size=Decimal("0.01"))
order_book_id = client._last_order_book_id
all_trade_ids: set[int] = set()
for i in range(5):
client.test_insert_order(
side=Side.BUY, price=Decimal("100.0"), quantity=10,
order_book_id=order_book_id, username=f"{self.test_name}_{i}_buy")
sell_resp = client.test_insert_order(
side=Side.SELL, price=Decimal("100.0"), quantity=10,
order_book_id=order_book_id, username=f"{self.test_name}_{i}_sell")
sell_response = sell_resp.get_response()
assert len(sell_response.trade_ids) == 1
trade_id = sell_response.trade_ids[0]
assert trade_id not in all_trade_ids, f"Duplicate trade_id {trade_id} on iteration {i}"
all_trade_ids.add(trade_id)
for trade_id in all_trade_ids:
client.expect_on_trade_event(
trade_id=trade_id, order_book_id=order_book_id,
buy_order_id=ANY, sell_order_id=ANY,
aggressive_side=Side.SELL, price=Decimal("100.0"), quantity=10)
self.call_expectations_manager.verify_expectations()
self.call_expectations_manager.verify_no_unexpected_calls()
# =========================================================================
# Matching no cross
# =========================================================================
def test_no_match_when_orders_do_not_cross(self) -> None:
"""A buy below the best ask should not trigger a trade."""
client = self._connect_unauthenticated()
self._create_order_book_via_admin(client, tick_size=Decimal("0.01"))
order_book_id = client._last_order_book_id
client.test_insert_order(
side=Side.BUY, price=Decimal("99.0"), quantity=10,
order_book_id=order_book_id, username=f"{self.test_name}_buyer")
sell_resp = client.test_insert_order(
side=Side.SELL, price=Decimal("101.0"), quantity=10,
order_book_id=order_book_id, username=f"{self.test_name}_seller")
sell_response = sell_resp.get_response()
assert len(sell_response.trade_ids) == 0, \
"Orders should not match when buy price < sell price"
assert sell_response.traded_quantity == 0
self.call_expectations_manager.verify_expectations()
self.call_expectations_manager.verify_no_unexpected_calls()
# =========================================================================
# Matching price priority
# =========================================================================
@pytest.mark.parametrize(
"aggressor_side",
[
pytest.param(Side.BUY, id="aggressive_buy_fills_best_ask_first"),
pytest.param(Side.SELL, id="aggressive_sell_fills_best_bid_first"),
]
)
def test_match_price_priority(self, aggressor_side: Side.ValueType) -> None:
"""The best-priced resting order must fill before worse-priced ones (price priority)."""
client_passive = self._connect_unauthenticated()
on_created = self._create_order_book_via_admin(client_passive, tick_size=Decimal("0.01"))
order_book_id = on_created.get_message().order_book_id
if aggressor_side == Side.BUY:
passive_side = Side.SELL
worse_resp = client_passive.test_insert_order(
side=passive_side, price=Decimal("101.0"), quantity=5,
order_book_id=order_book_id, username=f"{self.test_name}_worse")
better_resp = client_passive.test_insert_order(
side=passive_side, price=Decimal("100.0"), quantity=5,
order_book_id=order_book_id, username=f"{self.test_name}_better")
aggressive_price = Decimal("101.0")
expected_trade_price = Decimal("100.0")
else:
passive_side = Side.BUY
worse_resp = client_passive.test_insert_order(
side=passive_side, price=Decimal("99.0"), quantity=5,
order_book_id=order_book_id, username=f"{self.test_name}_worse")
better_resp = client_passive.test_insert_order(
side=passive_side, price=Decimal("100.0"), quantity=5,
order_book_id=order_book_id, username=f"{self.test_name}_better")
aggressive_price = Decimal("99.0")
expected_trade_price = Decimal("100.0")
worse_order_id = worse_resp.get_response().order_id
better_order_id = better_resp.get_response().order_id
client_aggressor = self._connect_unauthenticated()
client_aggressor.expect_on_order_book_created_event(
tick_size=Decimal("0.01"), order_book_id=order_book_id)
client_aggressor.expect_on_order_inserted_event(
order_book_id=order_book_id, order_id=worse_order_id,
side=passive_side, price=ANY, quantity=5)
client_aggressor.expect_on_order_inserted_event(
order_book_id=order_book_id, order_id=better_order_id,
side=passive_side, price=ANY, quantity=5)
self.call_expectations_manager.verify_expectations()
client_aggressor._last_order_book_id = order_book_id
agg_resp = client_aggressor.test_insert_order(
side=aggressor_side, price=aggressive_price, quantity=5,
order_book_id=order_book_id, username=f"{self.test_name}_agg")
agg_response = agg_resp.get_response()
assert len(agg_response.trade_ids) == 1, \
"Should match exactly one order (the better-priced one)"
assert agg_response.traded_quantity == 5
trade_id = agg_response.trade_ids[0]
if aggressor_side == Side.BUY:
expected_buy_id = agg_response.order_id
expected_sell_id = better_order_id
else:
expected_buy_id = better_order_id
expected_sell_id = agg_response.order_id
client_passive.expect_on_order_inserted_event(
order_book_id=order_book_id, order_id=agg_response.order_id,
side=aggressor_side, price=aggressive_price, quantity=5)
client_passive.expect_on_trade_event(
trade_id=trade_id, order_book_id=order_book_id,
buy_order_id=expected_buy_id, sell_order_id=expected_sell_id,
aggressive_side=aggressor_side, price=expected_trade_price, quantity=5)
client_aggressor.expect_on_trade_event(
trade_id=trade_id, order_book_id=order_book_id,
buy_order_id=expected_buy_id, sell_order_id=expected_sell_id,
aggressive_side=aggressor_side, price=expected_trade_price, quantity=5)
self.call_expectations_manager.verify_expectations()
self.call_expectations_manager.verify_no_unexpected_calls()
# =========================================================================
# Matching time priority (FIFO at the same price)
# =========================================================================
@pytest.mark.parametrize(
"aggressor_side",
[
pytest.param(Side.BUY, id="aggressive_buy_matches_oldest_ask"),
pytest.param(Side.SELL, id="aggressive_sell_matches_oldest_bid"),
]
)
def test_match_time_priority_at_same_price(self, aggressor_side: Side.ValueType) -> None:
"""Among resting orders at the same price, the earliest-inserted must fill first (FIFO)."""
client_passive = self._connect_unauthenticated()
on_created = self._create_order_book_via_admin(client_passive, tick_size=Decimal("0.01"))
order_book_id = on_created.get_message().order_book_id
passive_side = Side.SELL if aggressor_side == Side.BUY else Side.BUY
passive_price = Decimal("100.0")
first_resp = client_passive.test_insert_order(
side=passive_side, price=passive_price, quantity=5,
order_book_id=order_book_id, username=f"{self.test_name}_first")
first_order_id = first_resp.get_response().order_id
second_resp = client_passive.test_insert_order(
side=passive_side, price=passive_price, quantity=5,
order_book_id=order_book_id, username=f"{self.test_name}_second")
second_order_id = second_resp.get_response().order_id
client_aggressor = self._connect_unauthenticated()
client_aggressor.expect_on_order_book_created_event(
tick_size=Decimal("0.01"), order_book_id=order_book_id)
client_aggressor.expect_on_order_inserted_event(
order_book_id=order_book_id, order_id=first_order_id,
side=passive_side, price=passive_price, quantity=5)
client_aggressor.expect_on_order_inserted_event(
order_book_id=order_book_id, order_id=second_order_id,
side=passive_side, price=passive_price, quantity=5)
self.call_expectations_manager.verify_expectations()
client_aggressor._last_order_book_id = order_book_id
agg_resp = client_aggressor.test_insert_order(
side=aggressor_side, price=passive_price, quantity=5,
order_book_id=order_book_id, username=f"{self.test_name}_agg")
agg_response = agg_resp.get_response()
assert len(agg_response.trade_ids) == 1
assert agg_response.traded_quantity == 5
trade_id = agg_response.trade_ids[0]
if aggressor_side == Side.BUY:
expected_buy_id = agg_response.order_id
expected_sell_id = first_order_id
else:
expected_buy_id = first_order_id
expected_sell_id = agg_response.order_id
client_passive.expect_on_order_inserted_event(
order_book_id=order_book_id, order_id=agg_response.order_id,
side=aggressor_side, price=passive_price, quantity=5)
client_passive.expect_on_trade_event(
trade_id=trade_id, order_book_id=order_book_id,
buy_order_id=expected_buy_id, sell_order_id=expected_sell_id,
aggressive_side=aggressor_side, price=passive_price, quantity=5)
client_aggressor.expect_on_trade_event(
trade_id=trade_id, order_book_id=order_book_id,
buy_order_id=expected_buy_id, sell_order_id=expected_sell_id,
aggressive_side=aggressor_side, price=passive_price, quantity=5)
self.call_expectations_manager.verify_expectations()
self.call_expectations_manager.verify_no_unexpected_calls()
# =========================================================================
# Matching multi-client scenarios
# =========================================================================
def test_match_multiple_passive_orders_from_different_clients(self) -> None:
"""An aggressive order should sweep passive orders placed by different clients."""
client_a = self._connect_unauthenticated()
on_created = self._create_order_book_via_admin(client_a, tick_size=Decimal("0.01"))
order_book_id = on_created.get_message().order_book_id
resp_a = client_a.test_insert_order(
side=Side.BUY, price=Decimal("100.0"), quantity=10,
order_book_id=order_book_id, username=f"{self.test_name}_a")
order_id_a = resp_a.get_response().order_id
client_b = self._connect_unauthenticated()
client_b.expect_on_order_book_created_event(
tick_size=Decimal("0.01"), order_book_id=order_book_id)
client_b.expect_on_order_inserted_event(
order_book_id=order_book_id, order_id=order_id_a,
side=Side.BUY, price=Decimal("100.0"), quantity=10)
self.call_expectations_manager.verify_expectations()
client_b._last_order_book_id = order_book_id
resp_b = client_b.test_insert_order(
side=Side.BUY, price=Decimal("100.0"), quantity=10,
order_book_id=order_book_id, username=f"{self.test_name}_b")
order_id_b = resp_b.get_response().order_id
client_a.expect_on_order_inserted_event(
order_book_id=order_book_id, order_id=order_id_b,
side=Side.BUY, price=Decimal("100.0"), quantity=10)
self.call_expectations_manager.verify_expectations()
client_c = self._connect_unauthenticated()
client_c.expect_on_order_book_created_event(
tick_size=Decimal("0.01"), order_book_id=order_book_id)
client_c.expect_on_order_inserted_event(
order_book_id=order_book_id, order_id=order_id_a,
side=Side.BUY, price=Decimal("100.0"), quantity=10)
client_c.expect_on_order_inserted_event(
order_book_id=order_book_id, order_id=order_id_b,
side=Side.BUY, price=Decimal("100.0"), quantity=10)
self.call_expectations_manager.verify_expectations()
client_c._last_order_book_id = order_book_id
agg_resp = client_c.test_insert_order(
side=Side.SELL, price=Decimal("100.0"), quantity=20,
order_book_id=order_book_id, username=f"{self.test_name}_c")
agg_response = agg_resp.get_response()
assert len(agg_response.trade_ids) == 2, \
f"Expected 2 trades, got {len(agg_response.trade_ids)}"
assert agg_response.traded_quantity == 20
sell_order_id = agg_response.order_id
for client in [client_a, client_b]:
client.expect_on_order_inserted_event(
order_book_id=order_book_id, order_id=sell_order_id,
side=Side.SELL, price=Decimal("100.0"), quantity=20)
for trade_id in agg_response.trade_ids:
for client in [client_a, client_b, client_c]:
client.expect_on_trade_event(
trade_id=trade_id, order_book_id=order_book_id,
buy_order_id=ANY, sell_order_id=sell_order_id,
aggressive_side=Side.SELL, price=Decimal("100.0"), quantity=10)
self.call_expectations_manager.verify_expectations()
self.call_expectations_manager.verify_no_unexpected_calls()
def test_multiple_sequential_aggressive_orders_match_same_passive(self) -> None:
"""Multiple small aggressors should each partially fill a single large passive order."""
client1 = self._connect_unauthenticated()
on_created = self._create_order_book_via_admin(client1, tick_size=Decimal("0.01"))
order_book_id = on_created.get_message().order_book_id
buy_resp = client1.test_insert_order(
side=Side.BUY, price=Decimal("100.0"), quantity=30,
order_book_id=order_book_id, username=f"{self.test_name}_buyer")
buy_order_id = buy_resp.get_response().order_id
client2 = self._connect_unauthenticated()
client2.expect_on_order_book_created_event(
tick_size=Decimal("0.01"), order_book_id=order_book_id)
client2.expect_on_order_inserted_event(
order_book_id=order_book_id, order_id=buy_order_id,
side=Side.BUY, price=Decimal("100.0"), quantity=30)
self.call_expectations_manager.verify_expectations()
client2._last_order_book_id = order_book_id
all_trade_ids: list[int] = []
for i in range(3):
sell_resp = client2.test_insert_order(
side=Side.SELL, price=Decimal("100.0"), quantity=10,
order_book_id=order_book_id, username=f"{self.test_name}_{i}")
sell_response = sell_resp.get_response()
assert len(sell_response.trade_ids) == 1
assert sell_response.traded_quantity == 10
all_trade_ids.append(sell_response.trade_ids[0])
sell_order_id = sell_response.order_id
client1.expect_on_order_inserted_event(
order_book_id=order_book_id, order_id=sell_order_id,
side=Side.SELL, price=Decimal("100.0"), quantity=10)
client1.expect_on_trade_event(
trade_id=sell_response.trade_ids[0], order_book_id=order_book_id,
buy_order_id=buy_order_id, sell_order_id=sell_order_id,
aggressive_side=Side.SELL, price=Decimal("100.0"), quantity=10)
client2.expect_on_trade_event(
trade_id=sell_response.trade_ids[0], order_book_id=order_book_id,
buy_order_id=buy_order_id, sell_order_id=sell_order_id,
aggressive_side=Side.SELL, price=Decimal("100.0"), quantity=10)
self.call_expectations_manager.verify_expectations()
assert len(set(all_trade_ids)) == 3, "Each trade should have a unique ID"
self.call_expectations_manager.verify_no_unexpected_calls()
# =========================================================================
# Matching aggressive partial fill and remainder behaviour
# =========================================================================
def test_aggressive_partial_fill_then_match_remainder(self) -> None:
"""An aggressive order that partially fills should have its remainder matched by a later counterparty."""
client1 = self._connect_unauthenticated()
on_created = self._create_order_book_via_admin(client1, tick_size=Decimal("0.01"))
order_book_id = on_created.get_message().order_book_id
sell_resp = client1.test_insert_order(
side=Side.SELL, price=Decimal("100.0"), quantity=5,
order_book_id=order_book_id, username=f"{self.test_name}_1")
sell_order_id = sell_resp.get_response().order_id
client2 = self._connect_unauthenticated()
client2.expect_on_order_book_created_event(
tick_size=Decimal("0.01"), order_book_id=order_book_id)
client2.expect_on_order_inserted_event(
order_book_id=order_book_id, order_id=sell_order_id,
side=Side.SELL, price=Decimal("100.0"), quantity=5)
self.call_expectations_manager.verify_expectations()
client2._last_order_book_id = order_book_id
buy_resp = client2.test_insert_order(
side=Side.BUY, price=Decimal("100.0"), quantity=10,
order_book_id=order_book_id, username=f"{self.test_name}_2")
buy_response = buy_resp.get_response()
assert len(buy_response.trade_ids) == 1
assert buy_response.traded_quantity == 5
buy_order_id = buy_response.order_id
first_trade_id = buy_response.trade_ids[0]
client1.expect_on_order_inserted_event(
order_book_id=order_book_id, order_id=buy_order_id,
side=Side.BUY, price=Decimal("100.0"), quantity=10)
client1.expect_on_trade_event(
trade_id=first_trade_id, order_book_id=order_book_id,
buy_order_id=buy_order_id, sell_order_id=sell_order_id,
aggressive_side=Side.BUY, price=Decimal("100.0"), quantity=5)
client2.expect_on_trade_event(
trade_id=first_trade_id, order_book_id=order_book_id,
buy_order_id=buy_order_id, sell_order_id=sell_order_id,
aggressive_side=Side.BUY, price=Decimal("100.0"), quantity=5)
self.call_expectations_manager.verify_expectations()
sell_resp_2 = client1.test_insert_order(
side=Side.SELL, price=Decimal("100.0"), quantity=5,
order_book_id=order_book_id, username=f"{self.test_name}_1")
sell_response_2 = sell_resp_2.get_response()
assert len(sell_response_2.trade_ids) == 1
assert sell_response_2.traded_quantity == 5
sell_order_id_2 = sell_response_2.order_id
second_trade_id = sell_response_2.trade_ids[0]
client2.expect_on_order_inserted_event(
order_book_id=order_book_id, order_id=sell_order_id_2,
side=Side.SELL, price=Decimal("100.0"), quantity=5)
client1.expect_on_trade_event(
trade_id=second_trade_id, order_book_id=order_book_id,
buy_order_id=buy_order_id, sell_order_id=sell_order_id_2,
aggressive_side=Side.SELL, price=Decimal("100.0"), quantity=5)
client2.expect_on_trade_event(
trade_id=second_trade_id, order_book_id=order_book_id,
buy_order_id=buy_order_id, sell_order_id=sell_order_id_2,
aggressive_side=Side.SELL, price=Decimal("100.0"), quantity=5)
self.call_expectations_manager.verify_expectations()
self.call_expectations_manager.verify_no_unexpected_calls()
def test_aggressive_partial_fill_then_cancel_remainder(self) -> None:
"""After a partial fill, cancelling the remainder should succeed with the correct remaining_quantity."""
client1 = self._connect_unauthenticated()
on_created = self._create_order_book_via_admin(client1, tick_size=Decimal("0.01"))
order_book_id = on_created.get_message().order_book_id
client1.test_insert_order(
side=Side.SELL, price=Decimal("100.0"), quantity=5,
order_book_id=order_book_id, username=f"{self.test_name}_1")
sell_order_id = client1._last_order_id
client2 = self._connect_unauthenticated()
client2.expect_on_order_book_created_event(
tick_size=Decimal("0.01"), order_book_id=order_book_id)
client2.expect_on_order_inserted_event(
order_book_id=order_book_id, order_id=sell_order_id,
side=Side.SELL, price=Decimal("100.0"), quantity=5)
self.call_expectations_manager.verify_expectations()
client2._last_order_book_id = order_book_id
buy_resp = client2.test_insert_order(
side=Side.BUY, price=Decimal("100.0"), quantity=10,
order_book_id=order_book_id, username=f"{self.test_name}_2")
buy_response = buy_resp.get_response()
buy_order_id = buy_response.order_id
assert buy_response.traded_quantity == 5
trade_id = buy_response.trade_ids[0]
client1.expect_on_order_inserted_event(
order_book_id=order_book_id, order_id=buy_order_id,
side=Side.BUY, price=Decimal("100.0"), quantity=10)
client1.expect_on_trade_event(
trade_id=trade_id, order_book_id=order_book_id,
buy_order_id=buy_order_id, sell_order_id=sell_order_id,
aggressive_side=Side.BUY, price=Decimal("100.0"), quantity=5)
client2.expect_on_trade_event(
trade_id=trade_id, order_book_id=order_book_id,
buy_order_id=buy_order_id, sell_order_id=sell_order_id,
aggressive_side=Side.BUY, price=Decimal("100.0"), quantity=5)
self.call_expectations_manager.verify_expectations()
cancel_resp = client2.test_cancel_order(
order_id=buy_order_id, order_book_id=order_book_id)
cancel_response = cancel_resp.get_response()
assert cancel_response.remaining_quantity == 5, \
f"Expected remaining_quantity=5, got {cancel_response.remaining_quantity}"
client1.expect_on_order_cancelled_event(order_id=buy_order_id)
self.call_expectations_manager.verify_expectations()
self.call_expectations_manager.verify_no_unexpected_calls()
# =========================================================================
# Cancel prevents match
# =========================================================================
def test_cancel_passive_prevents_future_match(self) -> None:
"""Cancelling a passive order must prevent it from being matched by a later crossing order."""
client = self._connect_unauthenticated()
self._create_order_book_via_admin(client, tick_size=Decimal("0.01"))
order_book_id = client._last_order_book_id
buy_resp = client.test_insert_order(
side=Side.BUY, price=Decimal("100.0"), quantity=10,
order_book_id=order_book_id, username=f"{self.test_name}_buyer")
client.test_cancel_order(
order_id=buy_resp.get_response().order_id, order_book_id=order_book_id)
sell_resp = client.test_insert_order(
side=Side.SELL, price=Decimal("100.0"), quantity=10,
order_book_id=order_book_id, username=f"{self.test_name}_seller")
sell_response = sell_resp.get_response()
assert len(sell_response.trade_ids) == 0, \
"No trade should occur after the passive was cancelled"
assert sell_response.traded_quantity == 0
self.call_expectations_manager.verify_expectations()
self.call_expectations_manager.verify_no_unexpected_calls()
# =========================================================================
# Multiple order books isolation
# =========================================================================
def test_orders_on_different_order_books_do_not_interact(self) -> None:
"""Crossing orders in separate order books must not trigger a trade."""
client = self._connect_unauthenticated()
on_created_1 = self._create_order_book_via_admin(client, tick_size=Decimal("0.01"))
ob_id_1 = on_created_1.get_message().order_book_id
on_created_2 = self._create_order_book_via_admin(client, tick_size=Decimal("0.01"))
ob_id_2 = on_created_2.get_message().order_book_id
buy_resp = client.test_insert_order(
side=Side.BUY, price=Decimal("100.0"), quantity=10,
order_book_id=ob_id_1, username=f"{self.test_name}_1")
assert buy_resp.get_response().traded_quantity == 0
sell_resp = client.test_insert_order(
side=Side.SELL, price=Decimal("100.0"), quantity=10,
order_book_id=ob_id_2, username=f"{self.test_name}_2")
sell_response = sell_resp.get_response()
assert len(sell_response.trade_ids) == 0, \
"Orders in different order books should not match"
assert sell_response.traded_quantity == 0
self.call_expectations_manager.verify_expectations()
self.call_expectations_manager.verify_no_unexpected_calls()
def test_matching_independent_across_order_books(self) -> None:
"""A trade in one order book must not affect orders in another order book."""
client1 = self._connect_unauthenticated()
on_created_1 = self._create_order_book_via_admin(client1, tick_size=Decimal("0.01"))
ob_id_1 = on_created_1.get_message().order_book_id
on_created_2 = self._create_order_book_via_admin(client1, tick_size=Decimal("0.01"))
ob_id_2 = on_created_2.get_message().order_book_id
buy_resp_1 = client1.test_insert_order(
side=Side.BUY, price=Decimal("100.0"), quantity=10,
order_book_id=ob_id_1, username=f"{self.test_name}_1")
buy_order_1 = buy_resp_1.get_response().order_id
buy_resp_2 = client1.test_insert_order(
side=Side.BUY, price=Decimal("100.0"), quantity=10,
order_book_id=ob_id_2, username=f"{self.test_name}_1")
buy_order_2 = buy_resp_2.get_response().order_id
client2 = self._connect_unauthenticated()
client2.expect_on_order_book_created_event(
tick_size=Decimal("0.01"), order_book_id=ob_id_1)
client2.expect_on_order_book_created_event(
tick_size=Decimal("0.01"), order_book_id=ob_id_2)
client2.expect_on_order_inserted_event(
order_book_id=ob_id_1, order_id=buy_order_1,
side=Side.BUY, price=Decimal("100.0"), quantity=10)
client2.expect_on_order_inserted_event(
order_book_id=ob_id_2, order_id=buy_order_2,
side=Side.BUY, price=Decimal("100.0"), quantity=10)
self.call_expectations_manager.verify_expectations()
client2._last_order_book_id = ob_id_1
sell_resp = client2.test_insert_order(
side=Side.SELL, price=Decimal("100.0"), quantity=10,
order_book_id=ob_id_1, username=f"{self.test_name}_2")
sell_response = sell_resp.get_response()
assert sell_response.traded_quantity == 10
trade_id = sell_response.trade_ids[0]
sell_order_id = sell_response.order_id
client1.expect_on_order_inserted_event(
order_book_id=ob_id_1, order_id=sell_order_id,
side=Side.SELL, price=Decimal("100.0"), quantity=10)
client1.expect_on_trade_event(
trade_id=trade_id, order_book_id=ob_id_1,
buy_order_id=buy_order_1, sell_order_id=sell_order_id,
aggressive_side=Side.SELL, price=Decimal("100.0"), quantity=10)
client2.expect_on_trade_event(
trade_id=trade_id, order_book_id=ob_id_1,
buy_order_id=buy_order_1, sell_order_id=sell_order_id,
aggressive_side=Side.SELL, price=Decimal("100.0"), quantity=10)
self.call_expectations_manager.verify_expectations()
sell_resp_2 = client2.test_insert_order(
side=Side.SELL, price=Decimal("100.0"), quantity=10,
order_book_id=ob_id_2, username=f"{self.test_name}_2")
sell_response_2 = sell_resp_2.get_response()
assert sell_response_2.traded_quantity == 10, \
"Order on book 2 should still be available for matching"
sell_order_id_2 = sell_response_2.order_id
trade_id_2 = sell_response_2.trade_ids[0]
client1.expect_on_order_inserted_event(
order_book_id=ob_id_2, order_id=sell_order_id_2,
side=Side.SELL, price=Decimal("100.0"), quantity=10)
client1.expect_on_trade_event(
trade_id=trade_id_2, order_book_id=ob_id_2,
buy_order_id=buy_order_2, sell_order_id=sell_order_id_2,
aggressive_side=Side.SELL, price=Decimal("100.0"), quantity=10)
client2.expect_on_trade_event(
trade_id=trade_id_2, order_book_id=ob_id_2,
buy_order_id=buy_order_2, sell_order_id=sell_order_id_2,
aggressive_side=Side.SELL, price=Decimal("100.0"), quantity=10)
self.call_expectations_manager.verify_expectations()
self.call_expectations_manager.verify_no_unexpected_calls()
# =========================================================================
# Field propagation
# =========================================================================
def test_order_inserted_event_contains_username(self) -> None:
"""OnOrderInserted events must propagate the correct username."""
client1 = self._connect_unauthenticated()
on_created = self._create_order_book_via_admin(client1, tick_size=Decimal("0.01"))
order_book_id = on_created.get_message().order_book_id
test_username = f"{self.test_name}_user"
resp = client1.test_insert_order(
side=Side.BUY, price=Decimal("100.0"), quantity=10,
order_book_id=order_book_id, username=test_username)
order_id = resp.get_response().order_id
client2 = self._connect_unauthenticated()
client2.expect_on_order_book_created_event(
tick_size=Decimal("0.01"), order_book_id=order_book_id)
client2.expect_on_order_inserted_event(
order_book_id=order_book_id, order_id=order_id,
side=Side.BUY, price=Decimal("100.0"), quantity=10,
username=test_username)
self.call_expectations_manager.verify_expectations()
self.call_expectations_manager.verify_no_unexpected_calls()