from decimal import Decimal import logging import pytest from connection.tcp_connection_manager import TcpConnectionManager from proto.common_pb2 import Instrument, Side from proto.risk_limits_pb2 import ( InstrumentRiskLimits, RollingWindowLimit, UserRiskLimits, ) from tests.conftest import DEFAULT_PASSWORD from tests.common.auth_tests import AuthenticationTests from tests.common.mock_expectations import CallExpectationsManager from tests.test_client.admin_test_client import ( AdminClientConnectionHandlerFactory, AdminTestClient, ) from tests.test_client.risk_gateway_test_client import ( RiskGatewayClientConnectionHandlerFactory, RiskGatewayTestClient, ) logger = logging.getLogger(__name__) class TestRiskGatewaySystem(AuthenticationTests): PROTOCOL = "risk_limits" @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._client_factory = RiskGatewayClientConnectionHandlerFactory(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) -> RiskGatewayTestClient: return self.tcp_connection_manager.connect(self.server_address, self._client_factory) def _connect_and_login(self, username: str | None = None) -> RiskGatewayTestClient: """Connect to the risk gateway, log in (if auth required), and return the client.""" client: RiskGatewayTestClient = self.tcp_connection_manager.connect(self.server_address, self._client_factory) if self.auth_required: client.test_login(username=username or self.test_name, password=DEFAULT_PASSWORD) return client 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_instrument_via_admin(self, tick_size: Decimal = Decimal("0.01")) -> str: """Create an instrument via admin and return its symbol.""" symbol = f"TEST.{self._next_instrument_id}" self._next_instrument_id += 1 instrument = Instrument( symbol=symbol, description="Test instrument", currency="USD", multiplier=1.0) admin = self._get_admin_client() admin.test_create_instrument(instrument, tick_size) return symbol # ========================================================================= # Risk limits CRUD – user level # ========================================================================= def test_set_and_get_user_risk_limits(self) -> None: client = self._connect_and_login() limits = UserRiskLimits(max_outstanding_quantity=500) client.test_set_user_risk_limits(limits) resp = client.test_get_user_risk_limits() response = resp.get_response() assert response.user_risk_limits.max_outstanding_quantity == 500 def test_get_user_risk_limits_when_not_set(self) -> None: client = self._connect_and_login() client.test_get_user_risk_limits(expect_success=False) # ========================================================================= # Risk limits CRUD – instrument level # ========================================================================= def test_set_and_get_instrument_risk_limits(self) -> None: symbol = self._create_instrument_via_admin() client = self._connect_and_login() limits = InstrumentRiskLimits(max_outstanding_quantity=100) client.test_set_instrument_risk_limits(symbol, limits) resp = client.test_get_instrument_risk_limits() response = resp.get_response() assert symbol in response.risk_limits_by_instrument assert response.risk_limits_by_instrument[symbol].max_outstanding_quantity == 100 def test_get_instrument_risk_limits_when_not_set(self) -> None: client = self._connect_and_login() resp = client.test_get_instrument_risk_limits() response = resp.get_response() assert len(response.risk_limits_by_instrument) == 0 def test_set_instrument_risk_limits_for_multiple_instruments(self) -> None: sym_a = self._create_instrument_via_admin() sym_b = self._create_instrument_via_admin() client = self._connect_and_login() client.test_set_instrument_risk_limits( sym_a, InstrumentRiskLimits(max_outstanding_quantity=100)) client.test_set_instrument_risk_limits( sym_b, InstrumentRiskLimits(max_outstanding_quantity=200)) resp = client.test_get_instrument_risk_limits() by_instrument = resp.get_response().risk_limits_by_instrument assert by_instrument[sym_a].max_outstanding_quantity == 100 assert by_instrument[sym_b].max_outstanding_quantity == 200 # ========================================================================= # User max outstanding quantity # ========================================================================= def test_user_max_outstanding_quantity_accepts_within_limit(self) -> None: symbol = self._create_instrument_via_admin() client = self._connect_and_login() client.test_set_user_risk_limits( UserRiskLimits(max_outstanding_quantity=20)) client.test_insert_order(symbol, Side.BUY, Decimal("100.0"), 20) def test_user_max_outstanding_quantity_rejects_over_limit(self) -> None: symbol = self._create_instrument_via_admin() client = self._connect_and_login() client.test_set_user_risk_limits( UserRiskLimits(max_outstanding_quantity=20)) client.test_insert_order(symbol, Side.BUY, Decimal("100.0"), 20) client.test_insert_order(symbol, Side.BUY, Decimal("100.0"), 1, expect_success=False) def test_user_max_outstanding_quantity_freed_by_cancellation(self) -> None: symbol = self._create_instrument_via_admin() client = self._connect_and_login() client.test_set_user_risk_limits( UserRiskLimits(max_outstanding_quantity=20)) client.test_insert_order(symbol, Side.BUY, Decimal("100.0"), 20) client.test_cancel_order(symbol) client.test_insert_order(symbol, Side.BUY, Decimal("100.0"), 20) def test_user_max_outstanding_quantity_freed_by_full_trade(self) -> None: symbol = self._create_instrument_via_admin() user_a = self._connect_and_login(f"{self.test_name}_2") user_b = self._connect_and_login(f"{self.test_name}_3") user_a.test_set_user_risk_limits( UserRiskLimits(max_outstanding_quantity=20)) user_a.test_insert_order(symbol, Side.BUY, Decimal("100.0"), 20) user_a.test_insert_order(symbol, Side.BUY, Decimal("100.0"), 1, expect_success=False) user_b.test_insert_order(symbol, Side.SELL, Decimal("100.0"), 20) user_a.test_insert_order(symbol, Side.BUY, Decimal("100.0"), 20) def test_user_max_outstanding_quantity_freed_by_partial_trade(self) -> None: symbol = self._create_instrument_via_admin() user_a = self._connect_and_login(f"{self.test_name}_2") user_b = self._connect_and_login(f"{self.test_name}_3") user_a.test_set_user_risk_limits( UserRiskLimits(max_outstanding_quantity=20)) user_a.test_insert_order(symbol, Side.BUY, Decimal("100.0"), 20) user_b.test_insert_order(symbol, Side.SELL, Decimal("100.0"), 10) user_a.test_insert_order(symbol, Side.BUY, Decimal("100.0"), 10) def test_user_max_outstanding_quantity_spans_all_instruments(self) -> None: """The user-level limit applies across all instruments combined.""" sym_a = self._create_instrument_via_admin() sym_b = self._create_instrument_via_admin() client = self._connect_and_login() client.test_set_user_risk_limits( UserRiskLimits(max_outstanding_quantity=30)) client.test_insert_order(sym_a, Side.BUY, Decimal("100.0"), 20) client.test_insert_order(sym_b, Side.BUY, Decimal("100.0"), 10) client.test_insert_order(sym_b, Side.BUY, Decimal("100.0"), 1, expect_success=False) # ========================================================================= # Instrument max outstanding quantity # ========================================================================= def test_instrument_max_outstanding_quantity_rejects_over_limit(self) -> None: symbol = self._create_instrument_via_admin() client = self._connect_and_login() client.test_set_instrument_risk_limits( symbol, InstrumentRiskLimits(max_outstanding_quantity=15)) client.test_insert_order(symbol, Side.BUY, Decimal("100.0"), 15) client.test_insert_order(symbol, Side.BUY, Decimal("100.0"), 1, expect_success=False) def test_instrument_max_outstanding_quantity_independent_per_instrument(self) -> None: """Limits on instrument A must not affect orders on instrument B.""" sym_a = self._create_instrument_via_admin() sym_b = self._create_instrument_via_admin() client = self._connect_and_login() client.test_set_instrument_risk_limits( sym_a, InstrumentRiskLimits(max_outstanding_quantity=10)) client.test_insert_order(sym_a, Side.BUY, Decimal("100.0"), 10) client.test_insert_order(sym_a, Side.BUY, Decimal("100.0"), 1, expect_success=False) client.test_insert_order(sym_b, Side.BUY, Decimal("100.0"), 100) def test_instrument_max_outstanding_quantity_freed_by_cancellation(self) -> None: symbol = self._create_instrument_via_admin() client = self._connect_and_login() client.test_set_instrument_risk_limits( symbol, InstrumentRiskLimits(max_outstanding_quantity=10)) resp = client.test_insert_order(symbol, Side.BUY, Decimal("100.0"), 10) order_id = resp.get_response().order_id client.test_cancel_order(symbol, order_id=order_id) client.test_insert_order(symbol, Side.BUY, Decimal("100.0"), 10) def test_instrument_max_outstanding_quantity_freed_by_trade(self) -> None: symbol = self._create_instrument_via_admin() user_a = self._connect_and_login(f"{self.test_name}_2") user_b = self._connect_and_login(f"{self.test_name}_3") user_a.test_set_instrument_risk_limits( symbol, InstrumentRiskLimits(max_outstanding_quantity=10)) user_a.test_insert_order(symbol, Side.BUY, Decimal("100.0"), 10) user_b.test_insert_order(symbol, Side.SELL, Decimal("100.0"), 10) user_a.test_insert_order(symbol, Side.BUY, Decimal("100.0"), 10) # ========================================================================= # Instrument max outstanding amount # ========================================================================= def test_instrument_max_outstanding_amount_rejects_over_limit(self) -> None: symbol = self._create_instrument_via_admin() client = self._connect_and_login() client.test_set_instrument_risk_limits( symbol, InstrumentRiskLimits(max_outstanding_amount=1000.0)) client.test_insert_order(symbol, Side.BUY, Decimal("100.0"), 10) client.test_insert_order(symbol, Side.BUY, Decimal("100.0"), 1, expect_success=False) def test_instrument_max_outstanding_amount_considers_price_and_quantity(self) -> None: """Amount = price x quantity. A higher price hits the limit sooner.""" symbol = self._create_instrument_via_admin() client = self._connect_and_login() client.test_set_instrument_risk_limits( symbol, InstrumentRiskLimits(max_outstanding_amount=599.0)) client.test_insert_order(symbol, Side.BUY, Decimal("100.0"), 5) client.test_insert_order(symbol, Side.BUY, Decimal("10.0"), 10, expect_success=False) client.test_insert_order(symbol, Side.SELL, Decimal("100.0"), 1, expect_success=False) def test_instrument_max_outstanding_amount_freed_by_trade(self) -> None: symbol = self._create_instrument_via_admin() user_a = self._connect_and_login(f"{self.test_name}_2") user_b = self._connect_and_login(f"{self.test_name}_3") user_a.test_set_instrument_risk_limits( symbol, InstrumentRiskLimits(max_outstanding_amount=1000.0)) user_a.test_insert_order(symbol, Side.BUY, Decimal("100.0"), 10) user_a.test_insert_order(symbol, Side.BUY, Decimal("100.0"), 1, expect_success=False) user_b.test_insert_order(symbol, Side.SELL, Decimal("100.0"), 10) user_a.test_insert_order(symbol, Side.BUY, Decimal("100.0"), 10) # ========================================================================= # Message rate rolling limit # ========================================================================= def test_message_rate_limit_rejects_excess(self) -> None: """After hitting the message rate limit within the window, further orders are rejected.""" symbol = self._create_instrument_via_admin() client = self._connect_and_login() client.test_set_user_risk_limits(UserRiskLimits( message_rate_rolling_limit=RollingWindowLimit(limit=3, window_in_seconds=60))) client.test_insert_order(symbol, Side.BUY, Decimal("100.0"), 1) client.test_insert_order(symbol, Side.BUY, Decimal("101.0"), 1) client.test_insert_order(symbol, Side.BUY, Decimal("102.0"), 1) client.test_insert_order(symbol, Side.BUY, Decimal("103.0"), 1, expect_success=False) # ========================================================================= # Order quantity rolling limit # ========================================================================= def test_order_quantity_rolling_limit_rejects_excess(self) -> None: symbol = self._create_instrument_via_admin() client = self._connect_and_login() client.test_set_instrument_risk_limits(symbol, InstrumentRiskLimits( order_quantity_rolling_limit=RollingWindowLimit(limit=50, window_in_seconds=60))) client.test_insert_order(symbol, Side.BUY, Decimal("100.0"), 30) client.test_insert_order(symbol, Side.BUY, Decimal("101.0"), 20) client.test_insert_order(symbol, Side.BUY, Decimal("102.0"), 1, expect_success=False) # ========================================================================= # Order amount rolling limit # ========================================================================= def test_order_amount_rolling_limit_rejects_excess(self) -> None: symbol = self._create_instrument_via_admin() client = self._connect_and_login() client.test_set_instrument_risk_limits(symbol, InstrumentRiskLimits( order_amount_rolling_limit=RollingWindowLimit(limit=5000, window_in_seconds=60))) client.test_insert_order(symbol, Side.BUY, Decimal("100.0"), 30) client.test_insert_order(symbol, Side.BUY, Decimal("100.0"), 20) client.test_insert_order(symbol, Side.BUY, Decimal("100.0"), 1, expect_success=False) # ========================================================================= # Multi-user isolation # ========================================================================= def test_limits_are_isolated_between_users(self) -> None: """User A's limits must not affect user B's ability to trade.""" symbol = self._create_instrument_via_admin() user_a = self._connect_and_login(f"{self.test_name}_2") user_b = self._connect_and_login(f"{self.test_name}_3") user_a.test_set_user_risk_limits( UserRiskLimits(max_outstanding_quantity=10)) user_a.test_insert_order(symbol, Side.BUY, Decimal("100.0"), 10) user_a.test_insert_order(symbol, Side.BUY, Decimal("100.0"), 1, expect_success=False) user_b.test_insert_order(symbol, Side.SELL, Decimal("101.0"), 100) def test_order_from_user_a_does_not_count_against_user_b(self) -> None: """Outstanding orders from user A must not consume user B's limits.""" symbol = self._create_instrument_via_admin() user_a = self._connect_and_login(f"{self.test_name}_2") user_b = self._connect_and_login(f"{self.test_name}_3") user_b.test_set_instrument_risk_limits( symbol, InstrumentRiskLimits(max_outstanding_quantity=10)) user_a.test_insert_order(symbol, Side.BUY, Decimal("100.0"), 100) user_b.test_insert_order(symbol, Side.SELL, Decimal("101.0"), 10) # ========================================================================= # State tracking across trades and partial fills # ========================================================================= def test_partial_trade_correctly_updates_outstanding_quantity(self) -> None: """After a partial fill, the remaining quantity still counts towards limits.""" symbol = self._create_instrument_via_admin() user_a = self._connect_and_login(f"{self.test_name}_2") user_b = self._connect_and_login(f"{self.test_name}_3") user_a.test_set_instrument_risk_limits( symbol, InstrumentRiskLimits(max_outstanding_quantity=20)) user_a.test_insert_order(symbol, Side.BUY, Decimal("100.0"), 20) user_b.test_insert_order(symbol, Side.SELL, Decimal("100.0"), 5) user_a.test_insert_order(symbol, Side.BUY, Decimal("100.0"), 5) user_a.test_insert_order(symbol, Side.BUY, Decimal("100.0"), 1, expect_success=False) def test_trade_between_two_limited_users_updates_both(self) -> None: """When two users trade, both users' outstanding quantities must decrease.""" symbol = self._create_instrument_via_admin() user_a = self._connect_and_login(f"{self.test_name}_2") user_b = self._connect_and_login(f"{self.test_name}_3") user_a.test_set_user_risk_limits( UserRiskLimits(max_outstanding_quantity=10)) user_b.test_set_user_risk_limits( UserRiskLimits(max_outstanding_quantity=10)) user_a.test_insert_order(symbol, Side.BUY, Decimal("100.0"), 10) user_b.test_insert_order(symbol, Side.SELL, Decimal("100.0"), 10) user_a.test_insert_order(symbol, Side.BUY, Decimal("100.0"), 10) user_b.test_insert_order(symbol, Side.SELL, Decimal("101.0"), 10) def test_multiple_partial_fills_track_remaining_correctly(self) -> None: """Several small trades against a large order must reduce the outstanding by the cumulative traded amount.""" symbol = self._create_instrument_via_admin() user_a = self._connect_and_login(f"{self.test_name}_2") user_b = self._connect_and_login(f"{self.test_name}_3") user_a.test_set_instrument_risk_limits( symbol, InstrumentRiskLimits(max_outstanding_quantity=30)) user_a.test_insert_order(symbol, Side.BUY, Decimal("100.0"), 30) for _ in range(3): user_b.test_insert_order(symbol, Side.SELL, Decimal("100.0"), 5) user_a.test_insert_order(symbol, Side.BUY, Decimal("100.0"), 15) user_a.test_insert_order(symbol, Side.BUY, Decimal("100.0"), 1, expect_success=False) # ========================================================================= # Combined limits # ========================================================================= def test_both_user_and_instrument_limits_checked(self) -> None: """An order must satisfy both user-level and instrument-level limits.""" symbol = self._create_instrument_via_admin() client = self._connect_and_login() client.test_set_user_risk_limits( UserRiskLimits(max_outstanding_quantity=100)) client.test_set_instrument_risk_limits( symbol, InstrumentRiskLimits(max_outstanding_quantity=10)) client.test_insert_order(symbol, Side.BUY, Decimal("100.0"), 10) client.test_insert_order(symbol, Side.BUY, Decimal("100.0"), 1, expect_success=False) def test_instrument_limit_hit_while_user_limit_ok(self) -> None: """Even if the user-level limit has room, the instrument-level limit must block.""" sym_a = self._create_instrument_via_admin() sym_b = self._create_instrument_via_admin() client = self._connect_and_login() client.test_set_user_risk_limits( UserRiskLimits(max_outstanding_quantity=100)) client.test_set_instrument_risk_limits( sym_a, InstrumentRiskLimits(max_outstanding_quantity=5)) client.test_insert_order(sym_a, Side.BUY, Decimal("100.0"), 5) client.test_insert_order(sym_a, Side.BUY, Decimal("100.0"), 1, expect_success=False) client.test_insert_order(sym_b, Side.BUY, Decimal("100.0"), 50) def test_user_limit_hit_while_instrument_limit_ok(self) -> None: """Even if the instrument-level limit has room, the user-level limit must block.""" sym_a = self._create_instrument_via_admin() sym_b = self._create_instrument_via_admin() client = self._connect_and_login() client.test_set_user_risk_limits( UserRiskLimits(max_outstanding_quantity=15)) client.test_set_instrument_risk_limits( sym_a, InstrumentRiskLimits(max_outstanding_quantity=100)) client.test_set_instrument_risk_limits( sym_b, InstrumentRiskLimits(max_outstanding_quantity=100)) client.test_insert_order(sym_a, Side.BUY, Decimal("100.0"), 10) client.test_insert_order(sym_b, Side.BUY, Decimal("100.0"), 5) client.test_insert_order(sym_b, Side.BUY, Decimal("100.0"), 1, expect_success=False) # ========================================================================= # Floating-point precision (4-decimal-digit prices/amounts) # # The protocol uses double for prices and amounts. Naive use of IEEE 754 # floats leads to well-known rounding errors (e.g. 0.1 * 3 != 0.3). # These tests verify that the solution uses proper precision handling so # that orders exactly at the limit are not falsely rejected, and that # add/subtract cycles leave no residual error that blocks future orders. # ========================================================================= def test_amount_boundary_not_falsely_rejected_by_float_multiplication(self) -> None: """price * qty can overshoot in float (e.g. 0.1 * 3 = 0.30000000000000004). An order whose exact amount (4-decimal precision) equals the limit must be accepted despite IEEE 754 rounding in the multiplication. """ symbol = self._create_instrument_via_admin() client = self._connect_and_login() client.test_set_instrument_risk_limits( symbol, InstrumentRiskLimits(max_outstanding_amount=300.03)) # Exact: 100.01 x 3 = 300.03 — equals the limit, must be accepted. # In float: 100.01 * 3 may produce 300.03000000000003 due to the # inexact representation of 100.01 in IEEE 754. client.test_insert_order(symbol, Side.BUY, Decimal("100.01"), 3) def test_amount_boundary_not_falsely_rejected_by_float_accumulation(self) -> None: """Repeated float additions drift away from the true sum. Three additions of 33.3333 should total 99.9999, leaving room for exactly 0.0001 more. A naive float accumulation may overshoot 99.9999, falsely blocking the last order. """ symbol = self._create_instrument_via_admin(tick_size=Decimal("0.0001")) client = self._connect_and_login() client.test_set_instrument_risk_limits( symbol, InstrumentRiskLimits(max_outstanding_amount=100.0)) client.test_insert_order(symbol, Side.BUY, Decimal("33.3333"), 1) client.test_insert_order(symbol, Side.BUY, Decimal("33.3333"), 1) client.test_insert_order(symbol, Side.BUY, Decimal("33.3333"), 1) # Exact total so far: 99.9999. One more at 0.0001 → 100.0 = limit. client.test_insert_order(symbol, Side.BUY, Decimal("0.0001"), 1) def test_amount_boundary_with_classic_point_one_plus_point_two(self) -> None: """The textbook float error: 0.1 + 0.2 = 0.30000000000000004 in IEEE 754. With limit = 3000, two orders (0.1 x 10000 = 1000, 0.2 x 10000 = 2000) total exactly 3000. A third fills the boundary via small price. """ symbol = self._create_instrument_via_admin() client = self._connect_and_login() client.test_set_instrument_risk_limits( symbol, InstrumentRiskLimits(max_outstanding_amount=3000.0)) client.test_insert_order(symbol, Side.BUY, Decimal("0.1"), 10000) client.test_insert_order(symbol, Side.BUY, Decimal("0.2"), 10000) # Exact total: 1000 + 2000 = 3000 = limit. # Any additional order must be rejected. client.test_insert_order(symbol, Side.BUY, Decimal("0.01"), 1, expect_success=False) def test_amount_freed_correctly_after_cancelling_fractional_price_orders(self) -> None: """After inserting and cancelling orders at fractional prices, the full capacity must be available again — no residual float error may block. """ symbol = self._create_instrument_via_admin(tick_size=Decimal("0.0001")) client = self._connect_and_login() client.test_set_instrument_risk_limits( symbol, InstrumentRiskLimits(max_outstanding_amount=1000.0)) for price in ["33.3333", "33.3334", "33.3333"]: resp = client.test_insert_order(symbol, Side.BUY, Decimal(price), 1) order_id = resp.get_response().order_id client.test_cancel_order(symbol, order_id=order_id) # Full capacity must be restored despite float add/subtract drift. client.test_insert_order(symbol, Side.BUY, Decimal("100.0"), 10) def test_amount_freed_correctly_after_trades_at_fractional_prices(self) -> None: """After trades fully fill orders at fractional prices, the outstanding amount must return to zero so the full limit is available again. """ symbol = self._create_instrument_via_admin(tick_size=Decimal("0.0001")) user_a = self._connect_and_login(f"{self.test_name}_2") user_b = self._connect_and_login(f"{self.test_name}_3") user_a.test_set_instrument_risk_limits( symbol, InstrumentRiskLimits(max_outstanding_amount=1000.0)) # Insert and fully fill 3 orders at fractional prices. for price in ["33.3333", "33.3334", "33.3333"]: user_a.test_insert_order(symbol, Side.BUY, Decimal(price), 1) user_b.test_insert_order(symbol, Side.SELL, Decimal(price), 1) # Full capacity must be available again. user_a.test_insert_order(symbol, Side.BUY, Decimal("100.0"), 10) def test_amount_freed_correctly_after_partial_trade_at_fractional_price(self) -> None: """A partial fill at a fractional price must reduce the outstanding amount by exactly traded_qty x price, not by a drifted float value. """ symbol = self._create_instrument_via_admin(tick_size=Decimal("0.0001")) user_a = self._connect_and_login(f"{self.test_name}_2") user_b = self._connect_and_login(f"{self.test_name}_3") user_a.test_set_instrument_risk_limits( symbol, InstrumentRiskLimits(max_outstanding_amount=100.0)) # Insert qty 10 at 10.01 → amount = 100.1 > limit? No: 10.01 x 10 = 100.10 # Use a value that fits: 10.0 x 10 = 100.0 = limit user_a.test_insert_order(symbol, Side.BUY, Decimal("10.0001"), 5) # Outstanding: 50.0005. Partial fill 3 units → frees 30.0003. user_b.test_insert_order(symbol, Side.SELL, Decimal("10.0001"), 3) # Remaining outstanding: 20.0002. Room left: 79.9998. user_a.test_insert_order(symbol, Side.BUY, Decimal("79.9998"), 1) def test_order_amount_rolling_limit_boundary_with_fractional_multiplication(self) -> None: """The rolling-window amount sum must handle precision the same way as the outstanding amount — no false rejection at the exact boundary. """ symbol = self._create_instrument_via_admin() client = self._connect_and_login() client.test_set_instrument_risk_limits(symbol, InstrumentRiskLimits( order_amount_rolling_limit=RollingWindowLimit( limit=300, window_in_seconds=60))) # Exact: 100.01 x 3 = 300.03, but limit is 300 → rejected. # First fill up to just under the limit. client.test_insert_order(symbol, Side.BUY, Decimal("99.99"), 1) client.test_insert_order(symbol, Side.BUY, Decimal("99.99"), 1) client.test_insert_order(symbol, Side.BUY, Decimal("99.99"), 1) # Exact rolling total: 299.97. Room for 0.03 more. client.test_insert_order(symbol, Side.BUY, Decimal("0.01"), 3) # Now exact total: 299.97 + 0.03 = 300.0 = limit. Next must fail. client.test_insert_order(symbol, Side.BUY, Decimal("0.01"), 1, expect_success=False) def test_many_fractional_insert_cancel_cycles_leave_no_residual(self) -> None: """Repeated insert-cancel cycles at the same fractional price must not accumulate residual float error that eventually blocks orders. """ symbol = self._create_instrument_via_admin(tick_size=Decimal("0.0001")) client = self._connect_and_login() client.test_set_instrument_risk_limits( symbol, InstrumentRiskLimits(max_outstanding_amount=100.0)) for _ in range(20): resp = client.test_insert_order( symbol, Side.BUY, Decimal("33.3333"), 1) order_id = resp.get_response().order_id client.test_cancel_order(symbol, order_id=order_id) # After 20 round-trips, full capacity must still be available. client.test_insert_order(symbol, Side.BUY, Decimal("100.0"), 1)