polymarket-apis 0.2.2__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.

Potentially problematic release.


This version of polymarket-apis might be problematic. Click here for more details.

Files changed (40) hide show
  1. polymarket_apis/__init__.py +2 -0
  2. polymarket_apis/clients/__init__.py +0 -0
  3. polymarket_apis/clients/clob_client.py +730 -0
  4. polymarket_apis/clients/data_client.py +234 -0
  5. polymarket_apis/clients/gamma_client.py +311 -0
  6. polymarket_apis/clients/web3_client.py +261 -0
  7. polymarket_apis/clients/websockets_client.py +131 -0
  8. polymarket_apis/types/__init__.py +0 -0
  9. polymarket_apis/types/clob_types.py +494 -0
  10. polymarket_apis/types/common.py +49 -0
  11. polymarket_apis/types/data_types.py +161 -0
  12. polymarket_apis/types/gamma_types.py +313 -0
  13. polymarket_apis/types/websockets_types.py +191 -0
  14. polymarket_apis/utilities/__init__.py +0 -0
  15. polymarket_apis/utilities/config.py +36 -0
  16. polymarket_apis/utilities/constants.py +26 -0
  17. polymarket_apis/utilities/endpoints.py +37 -0
  18. polymarket_apis/utilities/exceptions.py +11 -0
  19. polymarket_apis/utilities/headers.py +54 -0
  20. polymarket_apis/utilities/order_builder/__init__.py +0 -0
  21. polymarket_apis/utilities/order_builder/builder.py +240 -0
  22. polymarket_apis/utilities/order_builder/helpers.py +61 -0
  23. polymarket_apis/utilities/signing/__init__.py +0 -0
  24. polymarket_apis/utilities/signing/eip712.py +28 -0
  25. polymarket_apis/utilities/signing/hmac.py +20 -0
  26. polymarket_apis/utilities/signing/model.py +8 -0
  27. polymarket_apis/utilities/signing/signer.py +25 -0
  28. polymarket_apis/utilities/web3/__init__.py +0 -0
  29. polymarket_apis/utilities/web3/abis/CTFExchange.json +1851 -0
  30. polymarket_apis/utilities/web3/abis/ConditionalTokens.json +705 -0
  31. polymarket_apis/utilities/web3/abis/NegRiskAdapter.json +999 -0
  32. polymarket_apis/utilities/web3/abis/NegRiskCtfExchange.json +1856 -0
  33. polymarket_apis/utilities/web3/abis/ProxyWalletFactory.json +319 -0
  34. polymarket_apis/utilities/web3/abis/UChildERC20Proxy.json +1438 -0
  35. polymarket_apis/utilities/web3/abis/__init__.py +0 -0
  36. polymarket_apis/utilities/web3/abis/custom_contract_errors.py +31 -0
  37. polymarket_apis/utilities/web3/helpers.py +8 -0
  38. polymarket_apis-0.2.2.dist-info/METADATA +18 -0
  39. polymarket_apis-0.2.2.dist-info/RECORD +40 -0
  40. polymarket_apis-0.2.2.dist-info/WHEEL +4 -0
@@ -0,0 +1,730 @@
1
+ import json
2
+ import logging
3
+ from datetime import UTC, datetime
4
+ from typing import Literal, Optional
5
+ from urllib.parse import urljoin
6
+
7
+ import httpx
8
+ from httpx import HTTPStatusError
9
+ from py_order_utils.model import SignedOrder
10
+
11
+ from ..types.clob_types import (
12
+ ApiCreds,
13
+ BidAsk,
14
+ BookParams,
15
+ ClobMarket,
16
+ CreateOrderOptions,
17
+ DailyEarnedReward,
18
+ MarketOrderArgs,
19
+ Midpoint,
20
+ OpenOrder,
21
+ OrderArgs,
22
+ OrderBookSummary,
23
+ OrderCancelResponse,
24
+ OrderPostResponse,
25
+ OrderType,
26
+ PaginatedResponse,
27
+ PartialCreateOrderOptions,
28
+ PolygonTrade,
29
+ PolymarketRewardItem,
30
+ PostOrdersArgs,
31
+ Price,
32
+ PriceHistory,
33
+ RequestArgs,
34
+ RewardsMarket,
35
+ Spread,
36
+ TickSize,
37
+ TokenBidAskDict,
38
+ TokenValueDict,
39
+ )
40
+ from ..types.common import EthAddress, Keccak256
41
+ from ..utilities.constants import END_CURSOR, POLYGON
42
+ from ..utilities.endpoints import (
43
+ ARE_ORDERS_SCORING,
44
+ CANCEL,
45
+ CANCEL_ALL,
46
+ CANCEL_ORDERS,
47
+ CREATE_API_KEY,
48
+ DELETE_API_KEY,
49
+ DERIVE_API_KEY,
50
+ GET_API_KEYS,
51
+ GET_LAST_TRADE_PRICE,
52
+ GET_LAST_TRADES_PRICES,
53
+ GET_MARKET,
54
+ GET_MARKETS,
55
+ GET_NEG_RISK,
56
+ GET_ORDER_BOOK,
57
+ GET_ORDER_BOOKS,
58
+ GET_PRICES,
59
+ GET_SPREAD,
60
+ GET_SPREADS,
61
+ GET_TICK_SIZE,
62
+ IS_ORDER_SCORING,
63
+ MID_POINT,
64
+ MID_POINTS,
65
+ ORDERS,
66
+ POST_ORDER,
67
+ POST_ORDERS,
68
+ PRICE,
69
+ TIME,
70
+ TRADES,
71
+ )
72
+ from ..utilities.exceptions import (
73
+ InvalidPriceError,
74
+ InvalidTickSizeError,
75
+ LiquidityError,
76
+ MissingOrderbookError,
77
+ )
78
+ from ..utilities.headers import create_level_1_headers, create_level_2_headers
79
+ from ..utilities.order_builder.builder import OrderBuilder
80
+ from ..utilities.order_builder.helpers import (
81
+ is_tick_size_smaller,
82
+ order_to_json,
83
+ price_valid,
84
+ )
85
+ from ..utilities.signing.signer import Signer
86
+
87
+ logger = logging.getLogger(__name__)
88
+
89
+ class PolymarketClobClient:
90
+ def __init__(
91
+ self,
92
+ private_key: str,
93
+ proxy_address: EthAddress,
94
+ creds: Optional[ApiCreds] = None,
95
+ chain_id: Literal[137, 80002] = POLYGON,
96
+ signature_type: Literal[0, 1, 2] = 1,
97
+ # 0 - EOA wallet, 1 - Proxy wallet, 2 - Gnosis Safe wallet
98
+ ):
99
+ self.proxy_address = proxy_address
100
+ self.client = httpx.Client(http2=True, timeout=30.0)
101
+ self.async_client = httpx.AsyncClient(http2=True, timeout=30.0)
102
+ self.base_url: str = "https://clob.polymarket.com"
103
+ self.signer = Signer(private_key=private_key, chain_id=chain_id)
104
+ self.builder = OrderBuilder(
105
+ signer=self.signer,
106
+ sig_type=signature_type,
107
+ funder=proxy_address,
108
+ )
109
+ self.creds = creds if creds else self.create_or_derive_api_creds()
110
+
111
+ # local cache
112
+ self.__tick_sizes = {}
113
+ self.__neg_risk = {}
114
+
115
+ def _build_url(self, endpoint: str) -> str:
116
+ return urljoin(self.base_url, endpoint)
117
+
118
+ def get_ok(self) -> str:
119
+ response = self.client.get(self.base_url)
120
+ response.raise_for_status()
121
+ return response.json()
122
+
123
+ def create_api_creds(self, nonce: Optional[int] = None) -> ApiCreds:
124
+ headers = create_level_1_headers(self.signer, nonce)
125
+ response = self.client.post(self._build_url(CREATE_API_KEY), headers=headers)
126
+ response.raise_for_status()
127
+ return ApiCreds(**response.json())
128
+
129
+ def derive_api_key(self, nonce: Optional[int] = None) -> ApiCreds:
130
+ headers = create_level_1_headers(self.signer, nonce)
131
+ response = self.client.get(self._build_url(DERIVE_API_KEY), headers=headers)
132
+ response.raise_for_status()
133
+ return ApiCreds(**response.json())
134
+
135
+ def create_or_derive_api_creds(self, nonce: Optional[int] = None) -> ApiCreds:
136
+ try:
137
+ return self.create_api_creds(nonce)
138
+ except HTTPStatusError:
139
+ return self.derive_api_key(nonce)
140
+
141
+ def set_api_creds(self, creds: ApiCreds) -> None:
142
+ self.creds = creds
143
+
144
+ def get_api_keys(self) -> dict:
145
+ request_args = RequestArgs(method="GET", request_path=GET_API_KEYS)
146
+ headers = create_level_2_headers(self.signer, self.creds, request_args)
147
+ response = self.client.get(self._build_url(GET_API_KEYS), headers=headers)
148
+ response.raise_for_status()
149
+ return response.json()
150
+
151
+ def delete_api_keys(self) -> Literal["OK"]:
152
+ request_args = RequestArgs(method="DELETE", request_path=DELETE_API_KEY)
153
+ headers = create_level_2_headers(self.signer, self.creds, request_args)
154
+ response = self.client.delete(self._build_url(DELETE_API_KEY), headers=headers)
155
+ response.raise_for_status()
156
+ return response.json()
157
+
158
+ def get_utc_time(self) -> datetime:
159
+ # parse server timestamp into utc datetime
160
+ response = self.client.get(self._build_url(TIME))
161
+ response.raise_for_status()
162
+ return datetime.fromtimestamp(response.json(), tz=UTC)
163
+
164
+ def get_tick_size(self, token_id: str) -> TickSize:
165
+ if token_id in self.__tick_sizes:
166
+ return self.__tick_sizes[token_id]
167
+
168
+ params = {"token_id": token_id}
169
+ response = self.client.get(self._build_url(GET_TICK_SIZE), params=params)
170
+ response.raise_for_status()
171
+ self.__tick_sizes[token_id] = str(response.json()["minimum_tick_size"])
172
+
173
+ return self.__tick_sizes[token_id]
174
+
175
+ def get_neg_risk(self, token_id: str) -> bool:
176
+ if token_id in self.__neg_risk:
177
+ return self.__neg_risk[token_id]
178
+
179
+ params = {"token_id": token_id}
180
+ response = self.client.get(self._build_url(GET_NEG_RISK), params=params)
181
+ response.raise_for_status()
182
+ self.__neg_risk[token_id] = response.json()["neg_risk"]
183
+
184
+ return self.__neg_risk[token_id]
185
+
186
+ def __resolve_tick_size(
187
+ self, token_id: str, tick_size: TickSize = None,
188
+ ) -> TickSize:
189
+ min_tick_size = self.get_tick_size(token_id)
190
+ if tick_size is not None:
191
+ if is_tick_size_smaller(tick_size, min_tick_size):
192
+ msg = f"invalid tick size ({tick_size!s}), minimum for the market is {min_tick_size!s}"
193
+ raise InvalidTickSizeError(msg)
194
+ else:
195
+ tick_size = min_tick_size
196
+ return tick_size
197
+
198
+ def get_midpoint(self, token_id: str) -> Midpoint:
199
+ """Get the mid-market price for the given token."""
200
+ params = {"token_id": token_id}
201
+ response = self.client.get(self._build_url(MID_POINT), params=params)
202
+ response.raise_for_status()
203
+ return Midpoint(token_id=token_id, value=float(response.json()["mid"]))
204
+
205
+ def get_midpoints(self, token_ids: list[str]) -> dict:
206
+ """Get the mid-market prices for a set of tokens."""
207
+ data = [{"token_id": token_id} for token_id in token_ids]
208
+ response = self.client.post(self._build_url(MID_POINTS), json=data)
209
+ response.raise_for_status()
210
+ return TokenValueDict(**response.json()).root
211
+
212
+ def get_spread(self, token_id: str) -> Spread:
213
+ """Get the spread for the given token."""
214
+ params = {"token_id": token_id}
215
+ response = self.client.get(self._build_url(GET_SPREAD), params=params)
216
+ response.raise_for_status()
217
+ return Spread(token_id=token_id, value=float(response.json()["mid"]))
218
+
219
+ def get_spreads(self, token_ids: list[str]) -> dict:
220
+ """Get the spreads for a set of tokens."""
221
+ data = [{"token_id": token_id} for token_id in token_ids]
222
+ response = self.client.post(self._build_url(GET_SPREADS), json=data)
223
+ response.raise_for_status()
224
+ return TokenValueDict(**response.json()).root
225
+
226
+ def get_price(self, token_id: str, side: Literal["BUY", "SELL"]) -> Price:
227
+ """Get the market price for the given token and side."""
228
+ params = {"token_id": token_id, "side": side}
229
+ response = self.client.get(self._build_url(PRICE), params=params)
230
+ response.raise_for_status()
231
+ return Price(**response.json(), token_id=token_id, side=side)
232
+
233
+ def get_prices(self, params: list[BookParams]) -> dict[str, BidAsk]:
234
+ """Get the market prices for a set of tokens and sides."""
235
+ data = [{"token_id": param.token_id, "side": param.side} for param in params]
236
+ response = self.client.post(self._build_url(GET_PRICES), json=data)
237
+ response.raise_for_status()
238
+ return TokenBidAskDict(**response.json()).root
239
+
240
+ def get_last_trade_price(self, token_id) -> Price:
241
+ """Fetches the last trade price for a token_id."""
242
+ params = {"token_id": token_id}
243
+ response = self.client.get(self._build_url(GET_LAST_TRADE_PRICE), params=params)
244
+ response.raise_for_status()
245
+ return Price(**response.json(), token_id=token_id)
246
+
247
+ def get_last_trades_prices(self, token_ids: list[str]) -> list[Price]:
248
+ """Fetches the last trades prices for a set of token ids."""
249
+ body = [{"token_id": token_id} for token_id in token_ids]
250
+ response = self.client.post(self._build_url(GET_LAST_TRADES_PRICES), json=body)
251
+ response.raise_for_status()
252
+ return [Price(**price) for price in response.json()]
253
+
254
+ def get_order_book(self, token_id) -> OrderBookSummary:
255
+ """Get the orderbook for the given token."""
256
+ params = {"token_id": token_id}
257
+ response = self.client.get(self._build_url(GET_ORDER_BOOK), params=params)
258
+ response.raise_for_status()
259
+ return OrderBookSummary(**response.json())
260
+
261
+ def get_order_books(self, token_ids: list[str]) -> list[OrderBookSummary]:
262
+ """Get the orderbook for a set of tokens."""
263
+ body = [{"token_id": token_id} for token_id in token_ids]
264
+ response = self.client.post(self._build_url(GET_ORDER_BOOKS), json=body)
265
+ response.raise_for_status()
266
+ return [OrderBookSummary(**obs) for obs in response.json()]
267
+
268
+ async def get_order_books_async(self, token_ids: list[str]) -> list[OrderBookSummary]:
269
+ """Get the orderbook for a set of tokens asynchronously."""
270
+ body = [{"token_id": token_id} for token_id in token_ids]
271
+ response = await self.async_client.post(self._build_url(GET_ORDER_BOOKS), json=body)
272
+ response.raise_for_status()
273
+ return [OrderBookSummary(**obs) for obs in response.json()]
274
+
275
+ def get_market(self, condition_id) -> ClobMarket:
276
+ """Get a ClobMarket by condition_id."""
277
+ response = self.client.get(self._build_url(GET_MARKET + condition_id))
278
+ response.raise_for_status()
279
+ return ClobMarket(**response.json())
280
+
281
+ def get_markets(self, next_cursor="MA==") -> PaginatedResponse[ClobMarket]:
282
+ """Get paginated ClobMarkets."""
283
+ params = {"next_cursor": next_cursor}
284
+ response = self.client.get(self._build_url(GET_MARKETS), params=params)
285
+ response.raise_for_status()
286
+ return PaginatedResponse[ClobMarket](**response.json())
287
+
288
+ def get_all_markets(self, next_cursor="MA==") -> list[ClobMarket]:
289
+ """Recursively fetch all ClobMarkets using pagination."""
290
+ # Base case: Stop recursion if next_cursor indicates the last page
291
+ if next_cursor == "LTE=":
292
+ print("Reached the last page of markets.")
293
+ return []
294
+
295
+ # Fetch current page of markets
296
+ paginated_response = self.get_markets(next_cursor=next_cursor)
297
+
298
+ # Collect current page data
299
+ current_markets = paginated_response.data
300
+
301
+ # Recursively fetch remaining pages
302
+ next_page_markets = self.get_all_markets(
303
+ next_cursor=paginated_response.next_cursor,
304
+ )
305
+
306
+ # Combine current page data with data from subsequent pages
307
+ return current_markets + next_page_markets
308
+
309
+ def get_recent_history(
310
+ self,
311
+ token_id: str,
312
+ interval: Optional[Literal["1d", "6h", "1h"]] = "1d",
313
+ fidelity: int = 1, # resolution in minutes
314
+ ) -> PriceHistory:
315
+ """Get the recent price history of a token (up to now) - 1h, 6h, 1d."""
316
+ if fidelity < 1:
317
+ msg = f"invalid filters: minimum 'fidelity' for '{interval}' range is 1"
318
+ raise ValueError(msg)
319
+
320
+ params = {
321
+ "market": token_id,
322
+ "interval": interval,
323
+ "fidelity": fidelity,
324
+ }
325
+ response = self.client.get(self._build_url("/prices-history"), params=params)
326
+ response.raise_for_status()
327
+ return PriceHistory(**response.json(), token_id=token_id)
328
+
329
+ def get_history(
330
+ self,
331
+ token_id: str,
332
+ start_time: Optional[datetime] = None,
333
+ end_time: Optional[datetime] = None,
334
+ interval: Optional[Literal["max", "1m", "1w"]] = "max",
335
+ fidelity: Optional[int] = 2, # resolution in minutes
336
+ ) -> PriceHistory:
337
+ """Get the price history of a token between selected dates - 1m, 1w, max."""
338
+ min_fidelities = {"1m": 10, "1w": 5, "max": 2}
339
+
340
+ if fidelity < min_fidelities[interval]:
341
+ msg = f"invalid filters: minimum 'fidelity' for '{interval}' range is {min_fidelities[interval]}"
342
+ raise ValueError(msg)
343
+
344
+ if start_time is None and end_time is None:
345
+ msg = "At least one of 'start_time' or 'end_time' must be provided."
346
+ raise ValueError(msg)
347
+
348
+ # Default values for timestamps if one is not provided
349
+
350
+ if start_time is None:
351
+ start_time = datetime(2020, 1, 1, tzinfo=UTC) # Default start time
352
+ if end_time is None:
353
+ end_time = datetime.now(UTC) # Default end time
354
+
355
+ params = {
356
+ "market": token_id,
357
+ "startTs": int(start_time.timestamp()),
358
+ "endTs": int(end_time.timestamp()),
359
+ "interval": interval,
360
+ "fidelity": fidelity,
361
+ }
362
+ response = self.client.get(self._build_url("/prices-history"), params=params)
363
+ response.raise_for_status()
364
+ return PriceHistory(**response.json(), token_id=token_id)
365
+
366
+ def get_orders(self, order_id: Optional[str] = None, condition_id: Optional[Keccak256] = None, token_id: Optional[str] = None, next_cursor: str ="MA==") -> list[OpenOrder]:
367
+ """Gets your active orders, filtered by order_id, condition_id, token_id."""
368
+ params = {}
369
+ if order_id:
370
+ params["id"] = order_id
371
+ if condition_id:
372
+ params["market"] = condition_id
373
+ if token_id:
374
+ params["asset_id"] = token_id
375
+
376
+ request_args = RequestArgs(method="GET", request_path=ORDERS)
377
+ headers = create_level_2_headers(self.signer, self.creds, request_args)
378
+
379
+ results = []
380
+ next_cursor = next_cursor if next_cursor is not None else "MA=="
381
+ while next_cursor != END_CURSOR:
382
+ params["next_cursor"] = next_cursor
383
+ response = self.client.get(self._build_url(ORDERS), headers=headers, params=params)
384
+ response.raise_for_status()
385
+ next_cursor = response.json()["next_cursor"]
386
+ results += [OpenOrder(**order) for order in response.json()["data"]]
387
+
388
+ return results
389
+
390
+ def create_order(self, order_args: OrderArgs, options: Optional[PartialCreateOrderOptions] = None) -> SignedOrder:
391
+ """Creates and signs an order."""
392
+ # add resolve_order_options, or similar
393
+ tick_size = self.__resolve_tick_size(
394
+ order_args.token_id,
395
+ options.tick_size if options else None,
396
+ )
397
+
398
+ if not price_valid(order_args.price, tick_size):
399
+ msg = f"price ({order_args.price}), min: {tick_size} - max: {1 - float(tick_size)}"
400
+ raise InvalidPriceError(msg)
401
+
402
+
403
+ neg_risk = (
404
+ options.neg_risk
405
+ if options and options.neg_risk
406
+ else self.get_neg_risk(order_args.token_id)
407
+ )
408
+
409
+ return self.builder.create_order(
410
+ order_args,
411
+ CreateOrderOptions(
412
+ tick_size=tick_size,
413
+ neg_risk=neg_risk,
414
+ ),
415
+ )
416
+
417
+ def post_order(self, order: SignedOrder, order_type: OrderType = OrderType.GTC) -> Optional[OrderPostResponse]:
418
+ """Posts a SignedOrder."""
419
+ body = order_to_json(order, self.creds.api_key, order_type)
420
+ headers = create_level_2_headers(
421
+ self.signer,
422
+ self.creds,
423
+ RequestArgs(method="POST", request_path=POST_ORDER, body=body),
424
+ )
425
+
426
+ try:
427
+ response = self.client.post(
428
+ self._build_url("/order"),
429
+ headers=headers,
430
+ content=json.dumps(body).encode("utf-8"),
431
+ )
432
+ response.raise_for_status()
433
+ return OrderPostResponse(**response.json())
434
+ except httpx.HTTPStatusError as exc:
435
+ msg = f"Client Error '{exc.response.status_code} {exc.response.reason_phrase}' while posting order"
436
+ logger.warning(msg)
437
+ error_json = exc.response.json()
438
+ print("Details:", error_json["error"])
439
+
440
+ def create_and_post_order(self, order_args: OrderArgs, options: Optional[PartialCreateOrderOptions] = None, order_type: OrderType = OrderType.GTC) -> OrderPostResponse:
441
+ """Utility function to create and publish an order."""
442
+ order = self.create_order(order_args, options)
443
+ return self.post_order(order=order, order_type=order_type)
444
+
445
+ def post_orders(self, args: list[PostOrdersArgs]):
446
+ """Posts multiple SignedOrders at once."""
447
+ body = [order_to_json(arg.order, self.creds.api_key, arg.order_type) for arg in args]
448
+ headers = create_level_2_headers(
449
+ self.signer,
450
+ self.creds,
451
+ RequestArgs(method="POST", request_path=POST_ORDERS, body=body),
452
+ )
453
+ try:
454
+ response = self.client.post(
455
+ self._build_url("/orders"),
456
+ headers=headers,
457
+ content=json.dumps(body).encode("utf-8"),
458
+ )
459
+ response.raise_for_status()
460
+ order_responses = []
461
+ for index, item in enumerate(response.json()):
462
+ resp = OrderPostResponse(**item)
463
+ order_responses.append(resp)
464
+ if resp.error_msg:
465
+ msg = (f"Error posting order in position {index} \n"
466
+ f"Details: {resp.error_msg}")
467
+ logger.warning(msg)
468
+ except httpx.HTTPStatusError as exc:
469
+ msg = f"Client Error '{exc.response.status_code} {exc.response.reason_phrase}' while posting order"
470
+ logger.warning(msg)
471
+ error_json = exc.response.json()
472
+ print("Details:", error_json["error"])
473
+ else:
474
+ return order_responses
475
+
476
+ def create_and_post_orders(self, args: list[OrderArgs], order_types: list[OrderType]) -> list[OrderPostResponse]:
477
+ """Utility function to create and publish multiple orders at once."""
478
+ return self.post_orders(
479
+ [PostOrdersArgs(order=self.create_order(order_args),
480
+ order_type=order_type)
481
+ for order_args, order_type in zip(args, order_types, strict=True)],
482
+ )
483
+
484
+ def calculate_market_price(self, token_id: str, side: str, amount: float, order_type: OrderType) -> float:
485
+ """Calculates the matching price considering an amount and the current orderbook."""
486
+ book = self.get_order_book(token_id)
487
+ if book is None:
488
+ msg = "Order book is None"
489
+ raise MissingOrderbookError(msg)
490
+ if side == "BUY":
491
+ if book.asks is None:
492
+ msg = "No ask orders available"
493
+ raise LiquidityError(msg)
494
+ return self.builder.calculate_buy_market_price(
495
+ book.asks, amount, order_type,
496
+ )
497
+ if book.bids is None:
498
+ msg = "No bid orders available"
499
+ raise LiquidityError(msg)
500
+ return self.builder.calculate_sell_market_price(
501
+ book.bids, amount, order_type,
502
+ )
503
+
504
+ def create_market_order(self, order_args: MarketOrderArgs, options: Optional[PartialCreateOrderOptions] = None):
505
+ """Creates and signs a market order."""
506
+ tick_size = self.__resolve_tick_size(
507
+ order_args.token_id,
508
+ options.tick_size if options else None,
509
+ )
510
+
511
+ if order_args.price is None or order_args.price <= 0:
512
+ order_args.price = self.calculate_market_price(
513
+ order_args.token_id,
514
+ order_args.side,
515
+ order_args.amount,
516
+ order_args.order_type,
517
+ )
518
+
519
+ if not price_valid(order_args.price, tick_size):
520
+ msg = f"price ({order_args.price}), min: {tick_size} - max: {1 - float(tick_size)}"
521
+ raise InvalidPriceError(msg)
522
+
523
+ neg_risk = (
524
+ options.neg_risk
525
+ if options and options.neg_risk
526
+ else self.get_neg_risk(order_args.token_id)
527
+ )
528
+
529
+ return self.builder.create_market_order(
530
+ order_args,
531
+ CreateOrderOptions(
532
+ tick_size=tick_size,
533
+ neg_risk=neg_risk,
534
+ ),
535
+ )
536
+
537
+ def create_and_post_market_order(
538
+ self,
539
+ order_args: MarketOrderArgs,
540
+ options: Optional[PartialCreateOrderOptions] = None,
541
+ order_type: OrderType = OrderType.FOK,
542
+ ) -> OrderPostResponse:
543
+ """Utility function to create and publish a market order."""
544
+ order = self.create_market_order(order_args, options)
545
+ return self.post_order(order=order, order_type=order_type)
546
+
547
+ def cancel_order(self, order_id: Keccak256) -> OrderCancelResponse:
548
+ """Cancels an order."""
549
+ body = {"orderID": order_id}
550
+
551
+ request_args = RequestArgs(method="DELETE", request_path=CANCEL, body=body)
552
+ headers = create_level_2_headers(self.signer, self.creds, request_args)
553
+
554
+ response = self.client.request("DELETE", self._build_url(CANCEL), headers=headers, data=json.dumps(body).encode("utf-8"))
555
+ response.raise_for_status()
556
+ return OrderCancelResponse(**response.json())
557
+
558
+ def cancel_orders(self, order_ids: list[Keccak256]) -> OrderCancelResponse:
559
+ """Cancels orders."""
560
+ body = order_ids
561
+
562
+ request_args = RequestArgs(
563
+ method="DELETE", request_path=CANCEL_ORDERS, body=body,
564
+ )
565
+ headers = create_level_2_headers(self.signer, self.creds, request_args)
566
+
567
+ response = self.client.request("DELETE", self._build_url(CANCEL_ORDERS), headers=headers, data=json.dumps(body).encode("utf-8"))
568
+ response.raise_for_status()
569
+ return OrderCancelResponse(**response.json())
570
+
571
+ def cancel_all(self) -> OrderCancelResponse:
572
+ """Cancels all available orders for the user."""
573
+ request_args = RequestArgs(method="DELETE", request_path=CANCEL_ALL)
574
+ headers = create_level_2_headers(self.signer, self.creds, request_args)
575
+
576
+ response = self.client.delete(self._build_url(CANCEL_ALL), headers=headers)
577
+ response.raise_for_status()
578
+ return OrderCancelResponse(**response.json())
579
+
580
+ def is_order_scoring(self, order_id: Keccak256) -> bool:
581
+ """Check if the order is currently scoring."""
582
+ request_args = RequestArgs(method="GET", request_path=IS_ORDER_SCORING)
583
+ headers = create_level_2_headers(self.signer, self.creds, request_args)
584
+
585
+ response = self.client.get(self._build_url(IS_ORDER_SCORING), headers=headers, params={"order_id": order_id})
586
+ response.raise_for_status()
587
+ return response.json()["scoring"]
588
+
589
+ def are_orders_scoring(self, order_ids: list[Keccak256]) -> dict[Keccak256, bool]:
590
+ """Check if the orders are currently scoring."""
591
+ body = order_ids
592
+ request_args = RequestArgs(
593
+ method="POST", request_path=ARE_ORDERS_SCORING, body=body,
594
+ )
595
+ headers = create_level_2_headers(self.signer, self.creds, request_args)
596
+ headers["Content-Type"] = "application/json"
597
+
598
+ response = self.client.post(self._build_url(ARE_ORDERS_SCORING), headers=headers, json=body)
599
+ response.raise_for_status()
600
+ return response.json()
601
+
602
+ def get_rewards_market(self, condition_id: Keccak256) -> RewardsMarket:
603
+ """
604
+ Get the RewardsMarket for a given market (condition_id).
605
+
606
+ - metadata, tokens, max_spread, min_size, rewards_config, market_competitiveness.
607
+ """
608
+ request_args = RequestArgs(method="GET", request_path="/rewards/markets/")
609
+ headers = create_level_2_headers(self.signer, self.creds, request_args)
610
+
611
+ response = self.client.get(self._build_url("/rewards/markets/" + condition_id), headers=headers)
612
+ response.raise_for_status()
613
+ return next(RewardsMarket(**market) for market in response.json()["data"])
614
+
615
+ def get_trades(
616
+ self,
617
+ condition_id: Keccak256 | None = None,
618
+ token_id: Optional[str] = None,
619
+ trade_id: Optional[str] = None,
620
+ before: Optional[datetime] = None,
621
+ after: Optional[datetime] = None,
622
+ maker_address: Optional[int] = None,
623
+ next_cursor="MA==") -> list[PolygonTrade]:
624
+ """Fetches the trade history for a user."""
625
+ params = {}
626
+ if condition_id:
627
+ params["market"] = condition_id
628
+ if token_id:
629
+ params["asset_id"] = token_id
630
+ if trade_id:
631
+ params["id"] = trade_id
632
+ if before:
633
+ params["before"] = int(before.replace(microsecond=0).timestamp())
634
+ if after:
635
+ params["after"] = int(after.replace(microsecond=0).timestamp())
636
+ if maker_address:
637
+ params["maker_address"] = maker_address
638
+
639
+ request_args = RequestArgs(method="GET", request_path=TRADES)
640
+ headers = create_level_2_headers(self.signer, self.creds, request_args)
641
+
642
+ results = []
643
+ next_cursor = next_cursor if next_cursor is not None else "MA=="
644
+ while next_cursor != END_CURSOR:
645
+ params["next_cursor"] = next_cursor
646
+ response = self.client.get(self._build_url(TRADES), headers=headers, params=params)
647
+ response.raise_for_status()
648
+ next_cursor = response.json()["next_cursor"]
649
+ results += [PolygonTrade(**trade) for trade in response.json()["data"]]
650
+
651
+ return results
652
+
653
+ def get_total_rewards(self, date: Optional[datetime] = None) -> DailyEarnedReward:
654
+ """Get the total rewards earned on a given date (seems to only hold the 6 most recent data points)."""
655
+ if date is None:
656
+ date = datetime.now(UTC)
657
+ params = {
658
+ "authenticationType": "magic",
659
+ "date": f"{date.strftime("%Y-%m-%d")}",
660
+ }
661
+
662
+ request_args = RequestArgs(method="GET", request_path="/rewards/user/total")
663
+ headers = create_level_2_headers(self.signer, self.creds, request_args)
664
+ params["l2Headers"] = json.dumps(headers)
665
+
666
+ response = self.client.get("https://polymarket.com/api/rewards/totalEarnings", params=params)
667
+ response.raise_for_status()
668
+ if response.json():
669
+ return DailyEarnedReward(**response.json()[0])
670
+ return DailyEarnedReward(
671
+ date=date,
672
+ asset_address="0x2791Bca1f2de4661ED88A30C99A7a9449Aa84174",
673
+ maker_address=self.proxy_address,
674
+ earnings=0.0,
675
+ asset_rate=0.0,
676
+ )
677
+
678
+ def get_reward_markets(
679
+ self,
680
+ sort_by: Optional[Literal["market", "max_spread", "min_size", "rate_per_day", "spread", "price", "earnings", "earning_percentage"]] = "market",
681
+ sort_direction: Optional[Literal["ASC", "DESC"]] = None,
682
+ query: Optional[str] = None,
683
+ show_favorites: bool = False,
684
+ ) -> list[PolymarketRewardItem]:
685
+ """
686
+ Get all polymarket.com/rewards items, sorted by different criteria.
687
+
688
+ - market start date ("market") - TODO confirm this
689
+ - max spread for rewards in usdc
690
+ - min size for rewards in shares
691
+ - reward rate per day in usdc
692
+ - current spread of a market
693
+ - current price of a market
694
+ - your daily earnings on a market - only need auth for these last two
695
+ - your current earning percentage on a market.
696
+ """
697
+ results = []
698
+ desc = {"ASC": False, "DESC": True}
699
+ params = {
700
+ "authenticationType": "magic",
701
+ "showFavorites": show_favorites,
702
+ }
703
+ if sort_by:
704
+ params["orderBy"] = sort_by
705
+ if query:
706
+ params["query"] = query
707
+ params["desc"] = False
708
+ if sort_direction:
709
+ params["desc"] = desc[sort_direction]
710
+
711
+ request_args = RequestArgs(method="GET", request_path="/rewards/user/markets")
712
+ headers = create_level_2_headers(self.signer, self.creds, request_args)
713
+ params["l2Headers"] = json.dumps(headers)
714
+
715
+ next_cursor = "MA=="
716
+ while next_cursor != END_CURSOR:
717
+ params["nextCursor"] = next_cursor
718
+ response = self.client.get("https://polymarket.com/api/rewards/markets", params=params)
719
+ # can probably use clob/rewards/user/markets here but haven't figure out auth
720
+ response.raise_for_status()
721
+ next_cursor = response.json()["next_cursor"]
722
+ results += [PolymarketRewardItem(**reward) for reward in response.json()["data"]]
723
+
724
+ return results
725
+
726
+ def __enter__(self):
727
+ return self
728
+
729
+ def __exit__(self, exc_type, exc_val, exc_tb):
730
+ self.client.close()