From 9ba391c929dc0966354b7c822df56736ec7a1bbf Mon Sep 17 00:00:00 2001 From: mark Date: Mon, 8 Jun 2026 18:37:58 +0100 Subject: [PATCH] Get builder trades --- .../create_limit_order_with_builder.py | 2 +- .../cases/buildercode/get_builder_trades.py | 39 +++++++++++++++++++ .../cases/createorder/create_market_order.py | 4 +- x10/clients/rest/modules/builder_module.py | 22 +++++++++++ x10/clients/rest/rest_api_client.py | 8 ++++ x10/models/trade.py | 20 ++++++++++ 6 files changed, 92 insertions(+), 3 deletions(-) create mode 100644 examples/cases/buildercode/get_builder_trades.py create mode 100644 x10/clients/rest/modules/builder_module.py diff --git a/examples/cases/buildercode/create_limit_order_with_builder.py b/examples/cases/buildercode/create_limit_order_with_builder.py index a01fe7b..1b5f562 100644 --- a/examples/cases/buildercode/create_limit_order_with_builder.py +++ b/examples/cases/buildercode/create_limit_order_with_builder.py @@ -22,7 +22,7 @@ async def run_example(): assert builder_id, "`builder_id` is not set" - markets_dict = await rest_client.markets_info.get_markets_dict() + markets_dict = await rest_client.info.get_markets_dict() fees = await rest_client.account.get_fees(market_names=[MARKET_NAME], builder_id=builder_id) builder_fee = fees.data[0].builder_fee_rate diff --git a/examples/cases/buildercode/get_builder_trades.py b/examples/cases/buildercode/get_builder_trades.py new file mode 100644 index 0000000..c9e3791 --- /dev/null +++ b/examples/cases/buildercode/get_builder_trades.py @@ -0,0 +1,39 @@ +import logging.handlers +from asyncio import run + +from examples.utils import create_rest_client, init_env + +LOGGER = logging.getLogger() + +PAGE_LIMIT = 100 + + +async def run_example(): + init_env() + rest_client = create_rest_client() + + LOGGER.info("Fetching builder trades (limit=%s)...", PAGE_LIMIT) + + response = await rest_client.builder.get_trades(limit=PAGE_LIMIT) + trades = response.data or [] + + LOGGER.info("Fetched %s trade(s)", len(trades)) + + for trade in trades: + LOGGER.info("Trade: %s", trade.to_pretty_json()) + + # Fetch the next page using the cursor returned in the pagination block. + if response.pagination and response.pagination.cursor: + next_cursor = response.pagination.cursor + + LOGGER.info("Fetching next page (cursor=%s)...", next_cursor) + + next_response = await rest_client.builder.get_trades(cursor=next_cursor, limit=PAGE_LIMIT) + + LOGGER.info("Fetched %s trade(s) on the next page", len(next_response.data or [])) + + await rest_client.close() + + +if __name__ == "__main__": + run(main=run_example()) diff --git a/examples/cases/createorder/create_market_order.py b/examples/cases/createorder/create_market_order.py index bc4fffc..0f2c061 100644 --- a/examples/cases/createorder/create_market_order.py +++ b/examples/cases/createorder/create_market_order.py @@ -12,8 +12,8 @@ async def run_example(): rest_client = create_rest_client() - markets_dict = await rest_client.markets_info.get_markets_dict() - market_stats = await rest_client.markets_info.get_market_statistics(market_name=MARKET_NAME) + markets_dict = await rest_client.info.get_markets_dict() + market_stats = await rest_client.info.get_market_statistics(market_name=MARKET_NAME) market = markets_dict[MARKET_NAME] diff --git a/x10/clients/rest/modules/builder_module.py b/x10/clients/rest/modules/builder_module.py new file mode 100644 index 0000000..a803aad --- /dev/null +++ b/x10/clients/rest/modules/builder_module.py @@ -0,0 +1,22 @@ +from typing import List, Optional + +from x10.clients.rest.modules.base_module import BaseModule +from x10.models.trade import BuilderTradeModel +from x10.utils.http import WrappedApiResponseModel, send_get_request + + +class BuilderModule(BaseModule): + async def get_trades( + self, + cursor: Optional[int] = None, + limit: Optional[int] = None, + ) -> WrappedApiResponseModel[List[BuilderTradeModel]]: + """ + Returns the trades attributed to the authenticated builder, newest first. + The response is paginated by trade id via the `cursor`. + """ + + url = self._get_url("/builder/trades", query={"cursor": cursor, "limit": limit}) + return await send_get_request( + await self._get_session(), url, List[BuilderTradeModel], api_key=self._get_api_key() + ) diff --git a/x10/clients/rest/rest_api_client.py b/x10/clients/rest/rest_api_client.py index 453e781..d3a9eb1 100644 --- a/x10/clients/rest/rest_api_client.py +++ b/x10/clients/rest/rest_api_client.py @@ -3,6 +3,7 @@ from typing import Dict, Optional from x10.clients.rest.modules.account_module import AccountModule +from x10.clients.rest.modules.builder_module import BuilderModule from x10.clients.rest.modules.info_module import InfoModule from x10.clients.rest.modules.order_management_module import OrderManagementModule from x10.clients.rest.modules.testnet_module import TestnetModule @@ -41,6 +42,7 @@ class RestApiClient: __order_management_module: OrderManagementModule __vault_module: VaultModule __testnet_module: TestnetModule + __builder_module: BuilderModule async def place_order( self, @@ -106,6 +108,7 @@ async def close(self): await self.__order_management_module.close_session() await self.__vault_module.close_session() await self.__testnet_module.close_session() + await self.__builder_module.close_session() async def __aenter__(self): return self @@ -131,6 +134,7 @@ def __init__(self, config: Config, stark_account: StarkPerpetualAccount | None = api_key=api_key, ) self.__testnet_module = TestnetModule(config, api_key=api_key, account_module=self.__account_module) + self.__builder_module = BuilderModule(config, api_key=api_key) @property def config(self): @@ -159,3 +163,7 @@ def testnet(self): @property def vault(self): return self.__vault_module + + @property + def builder(self): + return self.__builder_module diff --git a/x10/models/trade.py b/x10/models/trade.py index 556a1e1..a8aaa34 100644 --- a/x10/models/trade.py +++ b/x10/models/trade.py @@ -1,4 +1,5 @@ from decimal import Decimal +from typing import Optional from pydantic import AliasChoices, Field from strenum import StrEnum @@ -36,3 +37,22 @@ class AccountTradeModel(X10BaseModel): is_taker: bool trade_type: TradeType created_time: int + + +class BuilderTradeModel(X10BaseModel): + """ + A trade as seen by a builder. Only the side (maker/taker) that belongs to the + requesting builder is populated; the counterparty side is masked with `None`. + """ + + id: int + time: int + volume: Decimal + maker_id: Optional[int] = None + taker_id: Optional[int] = None + maker_builder_id: Optional[int] = None + taker_builder_id: Optional[int] = None + maker_fee: Optional[Decimal] = None + taker_fee: Optional[Decimal] = None + maker_builder_fee: Optional[Decimal] = None + taker_builder_fee: Optional[Decimal] = None