|  | 
| 38 | 38 | from nautilus_trader.model.currencies import USDC | 
| 39 | 39 | from nautilus_trader.model.currencies import USDC_POS | 
| 40 | 40 | from nautilus_trader.model.enums import AssetClass | 
|  | 41 | +from nautilus_trader.model.enums import LiquiditySide | 
| 41 | 42 | from nautilus_trader.model.enums import OrderSide | 
| 42 | 43 | from nautilus_trader.model.enums import OrderType | 
| 43 | 44 | from nautilus_trader.model.enums import TimeInForce | 
| @@ -1061,3 +1062,56 @@ def test_order_branching_logic(self): | 
| 1061 | 1062 |         # Assert order types are correctly identified | 
| 1062 | 1063 |         assert market_order.order_type == OrderType.MARKET | 
| 1063 | 1064 |         assert limit_order.order_type == OrderType.LIMIT | 
|  | 1065 | + | 
|  | 1066 | +    @pytest.mark.asyncio() | 
|  | 1067 | +    async def test_maker_fill_preserves_original_order_side(self, mocker): | 
|  | 1068 | +        """ | 
|  | 1069 | +        Regression test for issue #3126: Maker fill order side inversion when Yes/No | 
|  | 1070 | +        orders cross. | 
|  | 1071 | +
 | 
|  | 1072 | +        When a BUY order for "Yes" is matched against a BUY order for "No" | 
|  | 1073 | +        (complementary outcomes), the filled event should preserve the original order | 
|  | 1074 | +        side (BUY), not invert it based on the trade message. | 
|  | 1075 | +
 | 
|  | 1076 | +        """ | 
|  | 1077 | +        # Arrange - using maker order ID from user_trade1.json that matches our test wallet | 
|  | 1078 | +        instrument_id = get_polymarket_instrument_id( | 
|  | 1079 | +            "0xdd22472e552920b8438158ea7238bfadfa4f736aa4cee91a6b86c39ead110917", | 
|  | 1080 | +            "21742633143463906290569050155826241533067272736897614950488156847949938836455", | 
|  | 1081 | +        ) | 
|  | 1082 | +        order = self.strategy.order_factory.limit( | 
|  | 1083 | +            instrument_id=instrument_id, | 
|  | 1084 | +            order_side=OrderSide.BUY,  # Original order is BUY | 
|  | 1085 | +            quantity=Quantity.from_str("100"), | 
|  | 1086 | +            price=Price.from_str("0.518"), | 
|  | 1087 | +        ) | 
|  | 1088 | +        client_order_id = order.client_order_id | 
|  | 1089 | +        venue_order_id = VenueOrderId( | 
|  | 1090 | +            "0xab679e56242324e15e59cfd488cd0f12e4fd71b153b9bfb57518898b9983145e", | 
|  | 1091 | +        ) | 
|  | 1092 | + | 
|  | 1093 | +        self.cache.add_order(order, None) | 
|  | 1094 | +        self.cache.add_venue_order_id(client_order_id, venue_order_id) | 
|  | 1095 | +        filled_spy = mocker.spy(self.exec_client, "generate_order_filled") | 
|  | 1096 | + | 
|  | 1097 | +        # Act | 
|  | 1098 | +        raw_message = pkgutil.get_data( | 
|  | 1099 | +            package="tests.integration_tests.adapters.polymarket.resources.ws_messages", | 
|  | 1100 | +            resource="user_trade1.json", | 
|  | 1101 | +        ) | 
|  | 1102 | +        msg = msgspec.json.decode(raw_message) | 
|  | 1103 | +        msg = self.exec_client._decoder_user_msg.decode(msgspec.json.encode(msg)) | 
|  | 1104 | +        await self.exec_client._wait_for_ack_trade(msg, venue_order_id) | 
|  | 1105 | + | 
|  | 1106 | +        # Assert | 
|  | 1107 | +        filled_spy.assert_called_once() | 
|  | 1108 | +        call_kwargs = filled_spy.call_args.kwargs | 
|  | 1109 | + | 
|  | 1110 | +        # ASSERTION: order_side must match the original order (BUY), not be inverted | 
|  | 1111 | +        assert call_kwargs["order_side"] == OrderSide.BUY, ( | 
|  | 1112 | +            "Maker fill should preserve original order side (BUY), not invert it. " | 
|  | 1113 | +            "This ensures correct position tracking when Yes/No orders cross-match." | 
|  | 1114 | +        ) | 
|  | 1115 | +        assert call_kwargs["client_order_id"] == client_order_id | 
|  | 1116 | +        assert call_kwargs["venue_order_id"] == venue_order_id | 
|  | 1117 | +        assert call_kwargs["liquidity_side"] == LiquiditySide.MAKER | 
0 commit comments