From 0c809ac6e2da7f2969ed387f6b4ff4fbaeda79a1 Mon Sep 17 00:00:00 2001 From: markuspluna <59978114+markuspluna@users.noreply.github.com> Date: Tue, 12 May 2026 17:49:07 +0200 Subject: [PATCH] Add OrderRemovedEvent for the orderbook stream MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit tplus-core emits a `Removed` variant on /orders for every order that leaves the book (fully filled, canceled, GTD-expired, or rejected by the clearing engine; see tplus-core messages/orderbook_messages/src/events/order_removed.rs). The Python order-event parser was missing this model, so each Removed event raised "Unknown order event type: Removed" inside BaseClient._stream_ws and was silently dropped — masking real order-removal state from downstream consumers. Field set matches the Rust OrderRemoved struct exactly: order_id, asset_id, user_id, timestamp_ns, operator_pubkey, reason, filled_quantity, filled_amount, confirmed_quantity, confirmed_amount, book_quantity_decimals. `reason` is the OrderRemovalReason variant serialized as its name string ("Completed", "Canceled", "Expired", "Rejected"). Adds tests/model/test_order_events.py covering Removed parsing through parse_order_event for both a fully-filled (Completed) and a zero-fill (Canceled) shape. --- tests/model/test_order_events.py | 65 ++++++++++++++++++++++++++++++++ tplus/model/order.py | 17 +++++++++ 2 files changed, 82 insertions(+) create mode 100644 tests/model/test_order_events.py diff --git a/tests/model/test_order_events.py b/tests/model/test_order_events.py new file mode 100644 index 0000000..46b858b --- /dev/null +++ b/tests/model/test_order_events.py @@ -0,0 +1,65 @@ +from tplus.model.asset_identifier import AssetIdentifier +from tplus.model.order import ( + OrderRemovedEvent, + parse_order_event, +) + + +def test_parse_order_removed_event_completed(): + payload = { + "Removed": { + "order_id": "rt6G7V8gRAG4p7lfidkeUw==", + "asset_id": "82af49447d8a07e3bd95bd0d56f35241523fbab1000000000000000000000000@00000000000000a4b1", + "user_id": "0xabc", + "timestamp_ns": 1750146943779456128, + "operator_pubkey": "0xdef", + "reason": "Completed", + "filled_quantity": 100, + "filled_amount": 200, + "confirmed_quantity": 100, + "confirmed_amount": 200, + "book_quantity_decimals": 8, + } + } + + event = parse_order_event(payload) + + assert isinstance(event, OrderRemovedEvent) + assert event.event_type == "REMOVED" + assert event.order_id == "rt6G7V8gRAG4p7lfidkeUw==" + assert event.asset_id == AssetIdentifier( + "82af49447d8a07e3bd95bd0d56f35241523fbab1000000000000000000000000@00000000000000a4b1" + ) + assert event.user_id == "0xabc" + assert event.timestamp_ns == 1750146943779456128 + assert event.operator_pubkey == "0xdef" + assert event.reason == "Completed" + assert event.filled_quantity == 100 + assert event.filled_amount == 200 + assert event.confirmed_quantity == 100 + assert event.confirmed_amount == 200 + assert event.book_quantity_decimals == 8 + + +def test_parse_order_removed_event_canceled(): + payload = { + "Removed": { + "order_id": "abc", + "asset_id": "82af49447d8a07e3bd95bd0d56f35241523fbab1000000000000000000000000@00000000000000a4b1", + "user_id": "u", + "timestamp_ns": 0, + "operator_pubkey": "p", + "reason": "Canceled", + "filled_quantity": 0, + "filled_amount": 0, + "confirmed_quantity": 0, + "confirmed_amount": 0, + "book_quantity_decimals": 0, + } + } + + event = parse_order_event(payload) + + assert isinstance(event, OrderRemovedEvent) + assert event.reason == "Canceled" + assert event.filled_quantity == 0 diff --git a/tplus/model/order.py b/tplus/model/order.py index 1226e7f..a193ca9 100644 --- a/tplus/model/order.py +++ b/tplus/model/order.py @@ -198,6 +198,21 @@ class OrderCancelFailedEvent(BaseOrderEvent): reason: str | None = None +class OrderRemovedEvent(BaseOrderEvent): + event_type: Literal["REMOVED"] + order_id: str + asset_id: AssetIdentifier + user_id: str + timestamp_ns: int + operator_pubkey: str + reason: str + filled_quantity: int + filled_amount: int + confirmed_quantity: int + confirmed_amount: int + book_quantity_decimals: int + + OrderEvent = ( OrderCreatedEvent | OrderUpdatedEvent @@ -206,6 +221,7 @@ class OrderCancelFailedEvent(BaseOrderEvent): | OrderCreateFailedEvent | OrderReplaceFailedEvent | OrderCancelFailedEvent + | OrderRemovedEvent ) @@ -217,6 +233,7 @@ class OrderCancelFailedEvent(BaseOrderEvent): "CREATEFAILED": OrderCreateFailedEvent, "REPLACEFAILED": OrderReplaceFailedEvent, "CANCELFAILED": OrderCancelFailedEvent, + "REMOVED": OrderRemovedEvent, }