worm-sdk 0.9.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
worm_sdk/__init__.py ADDED
@@ -0,0 +1,62 @@
1
+ """Worm SDK — Python client for the Worm prediction markets platform."""
2
+
3
+ from worm_sdk._version import __version__
4
+ from worm_sdk.auth.signer import SignerProtocol
5
+ from worm_sdk.client import WormClient
6
+ from worm_sdk.exceptions import (
7
+ AuthenticationRequired,
8
+ SigningError,
9
+ WormAPIError,
10
+ WormSDKError,
11
+ )
12
+ from worm_sdk.types.auth import ApiKeyCredential
13
+ from worm_sdk.types.common import CursorPage, ResponseContext
14
+ from worm_sdk.types.enums import (
15
+ CandleInterval,
16
+ MarginActivityType,
17
+ MarginListSort,
18
+ MarginPolymarketMarketState,
19
+ MarginPositionRequestType,
20
+ MarginSettlementState,
21
+ MarketConfigKind,
22
+ MarketSortOption,
23
+ MarketStateFilter,
24
+ OrderSide,
25
+ OrderStatus,
26
+ OrderType,
27
+ PositionRequestState,
28
+ RedeemState,
29
+ SearchResultType,
30
+ SearchSortOption,
31
+ SearchStateFilter,
32
+ )
33
+
34
+ __all__ = [
35
+ "__version__",
36
+ "WormClient",
37
+ "SignerProtocol",
38
+ "ApiKeyCredential",
39
+ "CursorPage",
40
+ "ResponseContext",
41
+ "WormSDKError",
42
+ "WormAPIError",
43
+ "AuthenticationRequired",
44
+ "SigningError",
45
+ "MarginPositionRequestType",
46
+ "MarginListSort",
47
+ "MarginActivityType",
48
+ "MarginSettlementState",
49
+ "MarginPolymarketMarketState",
50
+ "MarketConfigKind",
51
+ "MarketSortOption",
52
+ "MarketStateFilter",
53
+ "CandleInterval",
54
+ "OrderSide",
55
+ "OrderStatus",
56
+ "OrderType",
57
+ "PositionRequestState",
58
+ "RedeemState",
59
+ "SearchResultType",
60
+ "SearchSortOption",
61
+ "SearchStateFilter",
62
+ ]
worm_sdk/_version.py ADDED
@@ -0,0 +1 @@
1
+ __version__ = "0.9.0"
@@ -0,0 +1,23 @@
1
+ from worm_sdk.api.account import AccountAPI
2
+ from worm_sdk.api.api_keys import ApiKeysAPI
3
+ from worm_sdk.api.events import EventsAPI
4
+ from worm_sdk.api.margin import MarginAPI
5
+ from worm_sdk.api.markets import MarketsAPI
6
+ from worm_sdk.api.orders import OrdersAPI
7
+ from worm_sdk.api.redeems import RedeemsAPI
8
+ from worm_sdk.api.search import SearchAPI
9
+ from worm_sdk.api.sports import SportsAPI
10
+ from worm_sdk.api.trades import TradesAPI
11
+
12
+ __all__ = [
13
+ "AccountAPI",
14
+ "ApiKeysAPI",
15
+ "EventsAPI",
16
+ "MarginAPI",
17
+ "MarketsAPI",
18
+ "OrdersAPI",
19
+ "RedeemsAPI",
20
+ "SearchAPI",
21
+ "SportsAPI",
22
+ "TradesAPI",
23
+ ]
@@ -0,0 +1,76 @@
1
+ """Account API — authenticated endpoints for user account data."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from typing import Any
6
+
7
+ from worm_sdk import constants
8
+ from worm_sdk.http import SyncHTTPClient
9
+ from worm_sdk.types.account import AccountPortfolioItem, AccountPnlBreakdown, AccountProfile
10
+ from worm_sdk.types.common import CursorPage
11
+
12
+
13
+ class AccountAPI:
14
+ """User account endpoints (authenticated)."""
15
+
16
+ def __init__(self, http: SyncHTTPClient) -> None:
17
+ self._http = http
18
+
19
+ def get_summary(self) -> AccountProfile:
20
+ """Return account profile fields for the authenticated user.
21
+
22
+ Includes ``username``, ``twitter_username``, and ``joined_at``.
23
+ """
24
+ data, _ = self._http.get(endpoint=constants.ACCOUNT_SUMMARY, authenticated=True)
25
+ return AccountProfile.model_validate(obj=data)
26
+
27
+ def get_assets(
28
+ self,
29
+ market_condition_id: str | None = None,
30
+ limit: int = 50,
31
+ cursor: str | None = None,
32
+ ) -> CursorPage[AccountPortfolioItem]:
33
+ """List spot market outcome share balances for the authenticated user.
34
+
35
+ Args:
36
+ market_condition_id: When set, return only share rows for this market.
37
+ limit: Maximum number of share rows to return.
38
+ cursor: Pagination cursor from a previous response.
39
+ """
40
+ params: dict[str, Any] = {"limit": limit}
41
+ if market_condition_id is not None:
42
+ params["market_condition_id"] = market_condition_id
43
+ if cursor is not None:
44
+ params["cursor"] = cursor
45
+
46
+ data, meta = self._http.get(endpoint=constants.ACCOUNT_ASSETS, params=params, authenticated=True)
47
+ items = [AccountPortfolioItem.model_validate(obj=row) for row in (data or [])]
48
+ return CursorPage[AccountPortfolioItem](
49
+ items=items,
50
+ next_cursor=meta.get("next_cursor"),
51
+ limit=meta.get("limit", limit),
52
+ )
53
+
54
+ def get_pnl(
55
+ self,
56
+ market_condition_id: str | None = None,
57
+ event_condition_id: str | None = None,
58
+ ) -> AccountPnlBreakdown:
59
+ """Return PnL breakdown for the authenticated user.
60
+
61
+ Args:
62
+ market_condition_id: Restrict PnL to a single market.
63
+ event_condition_id: Restrict PnL to a single event.
64
+ """
65
+ params: dict[str, Any] = {}
66
+ if market_condition_id is not None:
67
+ params["market_condition_id"] = market_condition_id
68
+ if event_condition_id is not None:
69
+ params["event_condition_id"] = event_condition_id
70
+
71
+ data, _ = self._http.get(
72
+ endpoint=constants.ACCOUNT_PNL,
73
+ params=params or None,
74
+ authenticated=True,
75
+ )
76
+ return AccountPnlBreakdown.model_validate(obj=data)
@@ -0,0 +1,29 @@
1
+ """API Keys API — authenticated endpoints for managing API keys."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from worm_sdk import constants
6
+ from worm_sdk.http import SyncHTTPClient
7
+ from worm_sdk.types.auth import ApiKeyListItem
8
+
9
+
10
+ class ApiKeysAPI:
11
+ """API key management endpoints (authenticated)."""
12
+
13
+ def __init__(self, http: SyncHTTPClient) -> None:
14
+ self._http = http
15
+
16
+ def list(self) -> list[ApiKeyListItem]:
17
+ """List API keys owned by the authenticated account."""
18
+ data, _ = self._http.get(endpoint=constants.AUTH_KEYS_LIST, authenticated=True)
19
+ rows = data or []
20
+ return [ApiKeyListItem.model_validate(obj=row) for row in rows]
21
+
22
+ def revoke(self, key_id: str) -> ApiKeyListItem:
23
+ """Revoke an API key by ``key_id``.
24
+
25
+ Revoked keys can no longer authenticate requests.
26
+ """
27
+ url = constants.AUTH_KEYS_REVOKE.format(key_id=key_id)
28
+ data, _ = self._http.delete(endpoint=url, authenticated=True)
29
+ return ApiKeyListItem.model_validate(obj=data)
worm_sdk/api/events.py ADDED
@@ -0,0 +1,69 @@
1
+ """Events API — public endpoints for event data."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from typing import Any
6
+
7
+ from worm_sdk import constants
8
+ from worm_sdk.http import SyncHTTPClient
9
+ from worm_sdk.types.common import CursorPage
10
+ from worm_sdk.types.enums import MarketSortOption, MarketStateFilter
11
+ from worm_sdk.types.event import Event, EventDetail
12
+ from worm_sdk.wire import enum_wire_value
13
+
14
+
15
+ class EventsAPI:
16
+ """Public event data endpoints."""
17
+
18
+ def __init__(self, http: SyncHTTPClient) -> None:
19
+ self._http = http
20
+
21
+ def list(
22
+ self,
23
+ state: MarketStateFilter | None = None,
24
+ category: str | None = None,
25
+ sort: MarketSortOption | str | None = None,
26
+ sport: str | None = None,
27
+ league: str | None = None,
28
+ limit: int = 20,
29
+ cursor: str | None = None,
30
+ ) -> CursorPage[Event]:
31
+ """List public events with filtering and cursor pagination.
32
+
33
+ Args:
34
+ state: Filter by market lifecycle state for events.
35
+ category: Filter by event category slug.
36
+ sort: Sort key: ``market_cap``, ``last_trade``, ``total_volume``, ``live_stream``,
37
+ ``trending``, ``leverage``, or ``ending_soon`` (see ``MarketSortOption``).
38
+ sport: Comma-separated sport slugs from ``client.sports.list()``.
39
+ league: Comma-separated league slugs from the sports catalog; requires ``sport``.
40
+ limit: Maximum number of events to return.
41
+ cursor: Pagination cursor from a previous response.
42
+ """
43
+ params: dict[str, Any] = {"limit": limit}
44
+ if cursor is not None:
45
+ params["cursor"] = cursor
46
+ if state is not None:
47
+ params["state"] = state.value
48
+ if category is not None:
49
+ params["category"] = category
50
+ if sort is not None:
51
+ params["sort"] = enum_wire_value(value=sort, enum_cls=MarketSortOption)
52
+ if sport is not None:
53
+ params["sport"] = sport
54
+ if league is not None:
55
+ params["league"] = league
56
+
57
+ data, meta = self._http.get(endpoint=constants.EVENTS_LIST, params=params)
58
+ items = [Event.model_validate(obj=e) for e in (data or [])]
59
+ return CursorPage[Event](
60
+ items=items,
61
+ next_cursor=meta.get("next_cursor"),
62
+ limit=meta.get("limit", limit),
63
+ )
64
+
65
+ def get(self, condition_id: str) -> EventDetail:
66
+ """Fetch full event details by event ``condition_id``."""
67
+ url = constants.EVENT_DETAIL.format(condition_id=condition_id)
68
+ data, _ = self._http.get(endpoint=url)
69
+ return EventDetail.model_validate(obj=data)
worm_sdk/api/margin.py ADDED
@@ -0,0 +1,388 @@
1
+ """Margin API — authenticated endpoints for margin position management."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from collections.abc import Iterable
6
+ from decimal import Decimal
7
+ from typing import Any
8
+
9
+ from worm_sdk import constants
10
+ from worm_sdk.auth.signer import SignerProtocol
11
+ from worm_sdk.http import SyncHTTPClient
12
+ from worm_sdk.types.common import CursorPage
13
+ from worm_sdk.types.enums import (
14
+ MarginListSort,
15
+ MarginPolymarketMarketState,
16
+ MarginPositionRequestType,
17
+ MarginSettlementState,
18
+ PositionRequestState,
19
+ )
20
+ from worm_sdk.types.margin import (
21
+ MarginCloseResponse,
22
+ MarginEstimate,
23
+ MarginSettlementClaimResponse,
24
+ Position,
25
+ PositionRequest,
26
+ PositionTpSl,
27
+ Settlement,
28
+ )
29
+ from worm_sdk.wire import comma_join_enum_wire, enum_wire_value
30
+
31
+
32
+ class MarginAPI:
33
+ """Margin trading endpoints."""
34
+
35
+ def __init__(self, http: SyncHTTPClient, signer: SignerProtocol | None = None) -> None:
36
+ self._http = http
37
+ self._signer = signer
38
+
39
+ # --- Public ---
40
+
41
+ def estimate(
42
+ self,
43
+ market_condition_id: str,
44
+ funds: Decimal | str | int,
45
+ leverage: Decimal | str | int | None = None,
46
+ is_yes: bool = True,
47
+ ) -> MarginEstimate:
48
+ """Estimate margin position metrics before opening a position.
49
+
50
+ Public endpoint; authentication is not required.
51
+ """
52
+ params: dict[str, Any] = {
53
+ "market_condition_id": market_condition_id,
54
+ "funds": str(funds),
55
+ "is_yes": is_yes,
56
+ }
57
+ if leverage is not None:
58
+ params["leverage"] = str(leverage)
59
+
60
+ data, _ = self._http.get(endpoint=constants.MARGIN_ESTIMATE, params=params)
61
+ return MarginEstimate.model_validate(obj=data)
62
+
63
+ # --- Positions ---
64
+
65
+ def list_positions(
66
+ self,
67
+ market_condition_id: str | None = None,
68
+ is_closed: bool | None = None,
69
+ sort: MarginListSort | str = MarginListSort.CREATED_DESC,
70
+ limit: int = 50,
71
+ cursor: str | None = None,
72
+ ) -> CursorPage[Position]:
73
+ """List margin positions for the authenticated user.
74
+
75
+ Args:
76
+ market_condition_id: Filter to a specific market.
77
+ is_closed: ``True`` for closed only, ``False`` for open only, ``None`` for both.
78
+ sort: ``created`` or ``-created`` (default ``-created``); see ``MarginListSort``.
79
+ limit: Maximum number of positions to return.
80
+ cursor: Pagination cursor from a previous response.
81
+ """
82
+ params: dict[str, Any] = {
83
+ "limit": limit,
84
+ "sort": enum_wire_value(value=sort, enum_cls=MarginListSort),
85
+ }
86
+ if cursor is not None:
87
+ params["cursor"] = cursor
88
+ if market_condition_id is not None:
89
+ params["market_condition_id"] = market_condition_id
90
+ if is_closed is not None:
91
+ params["is_closed"] = is_closed
92
+
93
+ data, meta = self._http.get(endpoint=constants.MARGIN_POSITIONS, params=params, authenticated=True)
94
+ items = [Position.model_validate(obj=row) for row in (data or [])]
95
+ return CursorPage[Position](
96
+ items=items,
97
+ next_cursor=meta.get("next_cursor"),
98
+ limit=meta.get("limit", limit),
99
+ )
100
+
101
+ def get_position(self, pubkey: str) -> Position:
102
+ """Fetch a single margin position by ``pubkey``."""
103
+ url = constants.MARGIN_POSITION_DETAIL.format(pubkey=pubkey)
104
+ data, _ = self._http.get(endpoint=url, authenticated=True)
105
+ return Position.model_validate(obj=data)
106
+
107
+ def close_position(
108
+ self,
109
+ pubkey: str,
110
+ price: Decimal | str | int | None = None,
111
+ ) -> MarginCloseResponse:
112
+ """Close a margin position.
113
+
114
+ Args:
115
+ pubkey: Position identifier.
116
+ price: Optional limit/target close price.
117
+ """
118
+ url = constants.MARGIN_POSITION_CLOSE.format(pubkey=pubkey)
119
+ params: dict[str, Any] = {}
120
+ if price is not None:
121
+ params["price"] = str(price)
122
+ data, _ = self._http.delete(endpoint=url, params=params or None, authenticated=True)
123
+ return MarginCloseResponse.model_validate(obj=data)
124
+
125
+ # --- Position Requests ---
126
+
127
+ def list_requests(
128
+ self,
129
+ market_condition_id: str | None = None,
130
+ states: PositionRequestState | str | Iterable[PositionRequestState] | None = None,
131
+ is_yes: bool | None = None,
132
+ leverage: Decimal | str | int | None = None,
133
+ sort: MarginListSort | str = MarginListSort.CREATED_DESC,
134
+ limit: int = 50,
135
+ cursor: str | None = None,
136
+ ) -> CursorPage[PositionRequest]:
137
+ """List margin position requests for the authenticated user.
138
+
139
+ Use this to inspect pending, submitted, filled, or canceled requests.
140
+
141
+ Args:
142
+ market_condition_id: Filter to a specific market.
143
+ states: Comma-separated request state filters (see ``PositionRequestState`` wire values)
144
+ or an iterable of enum members.
145
+ is_yes: Filter by outcome side.
146
+ leverage: Filter by leverage value.
147
+ sort: ``created`` or ``-created`` (default ``-created``); see ``MarginListSort``.
148
+ limit: Maximum number of requests to return.
149
+ cursor: Pagination cursor from a previous response.
150
+ """
151
+ params: dict[str, Any] = {
152
+ "limit": limit,
153
+ "sort": enum_wire_value(value=sort, enum_cls=MarginListSort),
154
+ }
155
+ if cursor is not None:
156
+ params["cursor"] = cursor
157
+ if market_condition_id is not None:
158
+ params["market_condition_id"] = market_condition_id
159
+ if states is not None:
160
+ params["states"] = comma_join_enum_wire(value=states, enum_cls=PositionRequestState)
161
+ if is_yes is not None:
162
+ params["is_yes"] = is_yes
163
+ if leverage is not None:
164
+ params["leverage"] = str(leverage)
165
+
166
+ data, meta = self._http.get(endpoint=constants.MARGIN_REQUESTS, params=params, authenticated=True)
167
+ items = [PositionRequest.model_validate(obj=row) for row in (data or [])]
168
+ return CursorPage[PositionRequest](
169
+ items=items,
170
+ next_cursor=meta.get("next_cursor"),
171
+ limit=meta.get("limit", limit),
172
+ )
173
+
174
+ def get_request(self, pubkey: str) -> PositionRequest:
175
+ """Fetch a single margin request by ``pubkey``."""
176
+ url = constants.MARGIN_REQUEST_DETAIL.format(pubkey=pubkey)
177
+ data, _ = self._http.get(endpoint=url, authenticated=True)
178
+ return PositionRequest.model_validate(obj=data)
179
+
180
+ def create_request(
181
+ self,
182
+ market_condition_id: str,
183
+ position_request_type: MarginPositionRequestType | str,
184
+ funds: Decimal | str | int | None = None,
185
+ is_yes: bool = True,
186
+ leverage: Decimal | str | int | None = None,
187
+ price: Decimal | str | int | None = None,
188
+ shares: Decimal | str | int | None = None,
189
+ take_profit_price: Decimal | str | int | None = None,
190
+ stop_loss_price: Decimal | str | int | None = None,
191
+ ) -> PositionRequest:
192
+ """Create an unsigned margin position request draft.
193
+
194
+ ``position_request_type`` must be ``market`` (pass ``funds``) or ``limit``
195
+ (pass ``price`` and ``shares``). The API rejects mixed combinations.
196
+
197
+ The response includes ``pubkey`` and signing ``message``. Sign and submit
198
+ with ``submit_request()``.
199
+ """
200
+ ptype = self._margin_request_type_json(value=position_request_type)
201
+ payload: dict[str, Any] = {
202
+ "type": ptype,
203
+ "market_condition_id": market_condition_id,
204
+ "is_yes": is_yes,
205
+ }
206
+ if leverage is not None:
207
+ payload["leverage"] = str(leverage)
208
+ if ptype == MarginPositionRequestType.MARKET.value:
209
+ if funds is None:
210
+ raise ValueError("funds is required when position_request_type is market")
211
+ payload["funds"] = str(funds)
212
+ else:
213
+ if price is None or shares is None:
214
+ raise ValueError("price and shares are required when position_request_type is limit")
215
+ payload["price"] = str(price)
216
+ payload["shares"] = str(shares)
217
+ if take_profit_price is not None:
218
+ payload["take_profit_price"] = str(take_profit_price)
219
+ if stop_loss_price is not None:
220
+ payload["stop_loss_price"] = str(stop_loss_price)
221
+
222
+ data, _ = self._http.post(endpoint=constants.MARGIN_REQUESTS, json=payload, authenticated=True)
223
+ return PositionRequest.model_validate(obj=data)
224
+
225
+ def cancel_request(self, pubkey: str) -> PositionRequest:
226
+ """Cancel a pending margin request by ``pubkey``."""
227
+ url = constants.MARGIN_REQUEST_DETAIL.format(pubkey=pubkey)
228
+ data, _ = self._http.delete(endpoint=url, authenticated=True)
229
+ return PositionRequest.model_validate(obj=data)
230
+
231
+ def submit_request(
232
+ self,
233
+ pubkey: str,
234
+ signature: str,
235
+ ) -> PositionRequest:
236
+ """Submit a hex signature over the position-request draft ``message``."""
237
+ url = constants.MARGIN_REQUEST_SUBMIT.format(pubkey=pubkey)
238
+ data, _ = self._http.post(
239
+ endpoint=url,
240
+ json={"signature": signature},
241
+ authenticated=True,
242
+ )
243
+ return PositionRequest.model_validate(obj=data)
244
+
245
+ def open_position(
246
+ self,
247
+ market_condition_id: str,
248
+ position_request_type: MarginPositionRequestType | str,
249
+ funds: Decimal | str | int | None = None,
250
+ is_yes: bool = True,
251
+ leverage: Decimal | str | int | None = None,
252
+ price: Decimal | str | int | None = None,
253
+ shares: Decimal | str | int | None = None,
254
+ take_profit_price: Decimal | str | int | None = None,
255
+ stop_loss_price: Decimal | str | int | None = None,
256
+ ) -> PositionRequest:
257
+ """Convenience: create request + auto-sign + submit in one call.
258
+
259
+ Requires a ``signer`` configured on ``WormClient``.
260
+ """
261
+ from worm_sdk.exceptions import SigningError
262
+
263
+ if self._signer is None:
264
+ raise SigningError("A signer is required for open_position(). Pass signer= to WormClient.")
265
+
266
+ request_data = self.create_request(
267
+ market_condition_id=market_condition_id,
268
+ position_request_type=position_request_type,
269
+ funds=funds,
270
+ is_yes=is_yes,
271
+ leverage=leverage,
272
+ price=price,
273
+ shares=shares,
274
+ take_profit_price=take_profit_price,
275
+ stop_loss_price=stop_loss_price,
276
+ )
277
+ message = request_data.message
278
+ if not message:
279
+ raise SigningError("Margin request draft did not include a signable message.")
280
+ if not request_data.pubkey:
281
+ raise SigningError("Margin request draft did not include a pubkey.")
282
+ signature = self._signer.sign_transaction(transaction_hex=message)
283
+ return self.submit_request(pubkey=request_data.pubkey, signature=signature)
284
+
285
+ # --- TP/SL ---
286
+
287
+ def set_tp_sl(
288
+ self,
289
+ pubkey: str,
290
+ take_profit_price: Decimal | str | int | None = None,
291
+ stop_loss_price: Decimal | str | int | None = None,
292
+ ) -> PositionTpSl:
293
+ """Set or update take-profit / stop-loss values on a position."""
294
+ payload: dict[str, Any] = {}
295
+ if take_profit_price is not None:
296
+ payload["take_profit_price"] = str(take_profit_price)
297
+ if stop_loss_price is not None:
298
+ payload["stop_loss_price"] = str(stop_loss_price)
299
+ if not any(k in payload for k in ("take_profit_price", "stop_loss_price")):
300
+ raise ValueError("Provide at least one of take_profit_price= or stop_loss_price=.")
301
+
302
+ url = constants.MARGIN_POSITION_TP_SL.format(pubkey=pubkey)
303
+ data, _ = self._http.post(endpoint=url, json=payload, authenticated=True)
304
+ return PositionTpSl.model_validate(obj=data)
305
+
306
+ def remove_tp_sl(self, pubkey: str) -> PositionTpSl:
307
+ """Remove both take-profit and stop-loss settings from a position."""
308
+ url = constants.MARGIN_POSITION_TP_SL.format(pubkey=pubkey)
309
+ data, _ = self._http.delete(endpoint=url, authenticated=True)
310
+ return PositionTpSl.model_validate(obj=data)
311
+
312
+ # --- Settlements ---
313
+
314
+ def claim_settlement(self, pubkey: str) -> MarginSettlementClaimResponse:
315
+ """Claim settlement funds for an eligible position."""
316
+ url = constants.MARGIN_POSITION_SETTLEMENTS_ACTION.format(pubkey=pubkey)
317
+ data, _ = self._http.post(endpoint=url, authenticated=True)
318
+ return MarginSettlementClaimResponse.model_validate(obj=data)
319
+
320
+ def list_settlements(
321
+ self,
322
+ position_pubkey: str | None = None,
323
+ market_condition_id: str | None = None,
324
+ states: MarginSettlementState | str | Iterable[MarginSettlementState] | None = None,
325
+ market_state: MarginPolymarketMarketState | str | None = None,
326
+ position_leverage: Decimal | str | int | None = None,
327
+ sort: MarginListSort | str = MarginListSort.CREATED_DESC,
328
+ limit: int = 50,
329
+ cursor: str | None = None,
330
+ ) -> CursorPage[Settlement]:
331
+ """List settlement records across the user's margin positions.
332
+
333
+ Args:
334
+ position_pubkey: Filter by margin position pubkey.
335
+ market_condition_id: Filter by market.
336
+ states: Comma-separated settlement state filters (see ``MarginSettlementState``) or
337
+ an iterable of enum members.
338
+ market_state: Market lifecycle filter for this list (``open`` / ``resolved``).
339
+ position_leverage: Filter by position leverage.
340
+ sort: ``created`` or ``-created`` (default ``-created``).
341
+ limit: Page size.
342
+ cursor: Pagination cursor.
343
+ """
344
+ params: dict[str, Any] = {
345
+ "limit": limit,
346
+ "sort": enum_wire_value(value=sort, enum_cls=MarginListSort),
347
+ }
348
+ if cursor is not None:
349
+ params["cursor"] = cursor
350
+ if position_pubkey is not None:
351
+ params["position_pubkey"] = position_pubkey
352
+ if market_condition_id is not None:
353
+ params["market_condition_id"] = market_condition_id
354
+ if states is not None:
355
+ params["states"] = comma_join_enum_wire(value=states, enum_cls=MarginSettlementState)
356
+ if market_state is not None:
357
+ params["market_state"] = self._normalize_market_state_wire(value=market_state)
358
+ if position_leverage is not None:
359
+ params["position_leverage"] = str(position_leverage)
360
+
361
+ data, meta = self._http.get(endpoint=constants.MARGIN_SETTLEMENTS, params=params, authenticated=True)
362
+ items = [Settlement.model_validate(obj=row) for row in (data or [])]
363
+ return CursorPage[Settlement](
364
+ items=items,
365
+ next_cursor=meta.get("next_cursor"),
366
+ limit=meta.get("limit", limit),
367
+ )
368
+
369
+ @staticmethod
370
+ def _margin_request_type_json(value: MarginPositionRequestType | str) -> str:
371
+ normalized = enum_wire_value(value=value, enum_cls=MarginPositionRequestType)
372
+ if normalized not in (
373
+ MarginPositionRequestType.MARKET.value,
374
+ MarginPositionRequestType.LIMIT.value,
375
+ ):
376
+ raise ValueError(f'position_request_type must be market or limit, got {value!r}')
377
+ return normalized
378
+
379
+ @staticmethod
380
+ def _normalize_market_state_wire(value: MarginPolymarketMarketState | str) -> str:
381
+ normalized = enum_wire_value(value=value, enum_cls=MarginPolymarketMarketState)
382
+ allowed = {
383
+ MarginPolymarketMarketState.OPEN.value,
384
+ MarginPolymarketMarketState.RESOLVED.value,
385
+ }
386
+ if normalized not in allowed:
387
+ raise ValueError(f"market_state must be open or resolved, got {value!r}")
388
+ return normalized