polymarket-apis 0.3.5__py3-none-any.whl → 0.3.7__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.
@@ -10,7 +10,7 @@ This package provides a comprehensive interface to Polymarket's APIs including:
10
10
  - GraphQL API for flexible data queries
11
11
  """
12
12
 
13
- __version__ = "0.3.5"
13
+ __version__ = "0.3.7"
14
14
  __author__ = "Razvan Gheorghe"
15
15
  __email__ = "razvan@gheorghe.me"
16
16
 
@@ -1,7 +1,7 @@
1
1
  import json
2
2
  import logging
3
3
  from datetime import UTC, datetime, timedelta
4
- from typing import Literal, Optional
4
+ from typing import Literal, cast
5
5
  from urllib.parse import urljoin
6
6
 
7
7
  import httpx
@@ -93,13 +93,13 @@ class PolymarketClobClient:
93
93
  def __init__(
94
94
  self,
95
95
  private_key: str,
96
- proxy_address: EthAddress,
97
- creds: Optional[ApiCreds] = None,
96
+ address: EthAddress,
97
+ creds: ApiCreds | None = None,
98
98
  chain_id: Literal[137, 80002] = POLYGON,
99
99
  signature_type: Literal[0, 1, 2] = 1,
100
100
  # 0 - EOA wallet, 1 - Proxy wallet, 2 - Gnosis Safe wallet
101
101
  ):
102
- self.proxy_address = proxy_address
102
+ self.address = address
103
103
  self.client = httpx.Client(http2=True, timeout=30.0)
104
104
  self.async_client = httpx.AsyncClient(http2=True, timeout=30.0)
105
105
  self.base_url: str = "https://clob.polymarket.com"
@@ -107,14 +107,14 @@ class PolymarketClobClient:
107
107
  self.builder = OrderBuilder(
108
108
  signer=self.signer,
109
109
  sig_type=signature_type,
110
- funder=proxy_address,
110
+ funder=address,
111
111
  )
112
112
  self.creds = creds if creds else self.create_or_derive_api_creds()
113
113
 
114
114
  # local cache
115
- self.__tick_sizes = {}
116
- self.__neg_risk = {}
117
- self.__fee_rates = {}
115
+ self.__tick_sizes: dict[str, TickSize] = {}
116
+ self.__neg_risk: dict[str, bool] = {}
117
+ self.__fee_rates: dict[str, int] = {}
118
118
 
119
119
  def _build_url(self, endpoint: str) -> str:
120
120
  return urljoin(self.base_url, endpoint)
@@ -124,19 +124,19 @@ class PolymarketClobClient:
124
124
  response.raise_for_status()
125
125
  return response.json()
126
126
 
127
- def create_api_creds(self, nonce: Optional[int] = None) -> ApiCreds:
127
+ def create_api_creds(self, nonce: int | None = None) -> ApiCreds:
128
128
  headers = create_level_1_headers(self.signer, nonce)
129
129
  response = self.client.post(self._build_url(CREATE_API_KEY), headers=headers)
130
130
  response.raise_for_status()
131
131
  return ApiCreds(**response.json())
132
132
 
133
- def derive_api_key(self, nonce: Optional[int] = None) -> ApiCreds:
133
+ def derive_api_key(self, nonce: int | None = None) -> ApiCreds:
134
134
  headers = create_level_1_headers(self.signer, nonce)
135
135
  response = self.client.get(self._build_url(DERIVE_API_KEY), headers=headers)
136
136
  response.raise_for_status()
137
137
  return ApiCreds(**response.json())
138
138
 
139
- def create_or_derive_api_creds(self, nonce: Optional[int] = None) -> ApiCreds:
139
+ def create_or_derive_api_creds(self, nonce: int | None = None) -> ApiCreds:
140
140
  try:
141
141
  return self.create_api_creds(nonce)
142
142
  except HTTPStatusError:
@@ -172,7 +172,9 @@ class PolymarketClobClient:
172
172
  params = {"token_id": token_id}
173
173
  response = self.client.get(self._build_url(GET_TICK_SIZE), params=params)
174
174
  response.raise_for_status()
175
- self.__tick_sizes[token_id] = str(response.json()["minimum_tick_size"])
175
+ self.__tick_sizes[token_id] = cast(
176
+ "TickSize", str(response.json()["minimum_tick_size"])
177
+ )
176
178
 
177
179
  return self.__tick_sizes[token_id]
178
180
 
@@ -202,7 +204,7 @@ class PolymarketClobClient:
202
204
  def __resolve_tick_size(
203
205
  self,
204
206
  token_id: str,
205
- tick_size: TickSize = None,
207
+ tick_size: TickSize | None = None,
206
208
  ) -> TickSize:
207
209
  min_tick_size = self.get_tick_size(token_id)
208
210
  if tick_size is not None:
@@ -216,7 +218,7 @@ class PolymarketClobClient:
216
218
  def __resolve_fee_rate(
217
219
  self,
218
220
  token_id: str,
219
- user_fee_rate: Optional[int] = None,
221
+ user_fee_rate: int | None = None,
220
222
  ) -> int:
221
223
  market_fee_rate_bps = self.get_fee_rate_bps(token_id)
222
224
  # If both fee rate on the market and the user supplied fee rate are non-zero, validate that they match
@@ -362,11 +364,11 @@ class PolymarketClobClient:
362
364
  "max": 2,
363
365
  }
364
366
 
365
- if fidelity < min_fidelities.get(interval):
366
- msg = f"invalid filters: minimum 'fidelity' for '{interval}' range is {min_fidelities.get(interval)}"
367
+ if fidelity < min_fidelities[interval]:
368
+ msg = f"invalid filters: minimum fidelity' for '{interval}' range is {min_fidelities.get(interval)}"
367
369
  raise ValueError(msg)
368
370
 
369
- params = {
371
+ params: dict[str, int | str] = {
370
372
  "market": token_id,
371
373
  "interval": interval,
372
374
  "fidelity": fidelity,
@@ -378,8 +380,8 @@ class PolymarketClobClient:
378
380
  def get_history(
379
381
  self,
380
382
  token_id: str,
381
- start_time: Optional[datetime] = None,
382
- end_time: Optional[datetime] = None,
383
+ start_time: datetime | None = None,
384
+ end_time: datetime | None = None,
383
385
  fidelity: int = 2, # resolution in minutes
384
386
  ) -> PriceHistory:
385
387
  """Get the price history of a token between a selected date range of max 15 days or from start_time to now."""
@@ -395,7 +397,7 @@ class PolymarketClobClient:
395
397
  msg = "'start_time' - 'end_time' range cannot exceed 15 days. Remove 'end_time' to get prices up to now or set a shorter range."
396
398
  raise ValueError(msg)
397
399
 
398
- params = {
400
+ params: dict[str, int | str] = {
399
401
  "market": token_id,
400
402
  "fidelity": fidelity,
401
403
  }
@@ -417,9 +419,9 @@ class PolymarketClobClient:
417
419
 
418
420
  def get_orders(
419
421
  self,
420
- order_id: Optional[str] = None,
421
- condition_id: Optional[Keccak256] = None,
422
- token_id: Optional[str] = None,
422
+ order_id: str | None = None,
423
+ condition_id: Keccak256 | None = None,
424
+ token_id: str | None = None,
423
425
  next_cursor: str = "MA==",
424
426
  ) -> list[OpenOrder]:
425
427
  """Gets your active orders, filtered by order_id, condition_id, token_id."""
@@ -448,7 +450,7 @@ class PolymarketClobClient:
448
450
  return results
449
451
 
450
452
  def create_order(
451
- self, order_args: OrderArgs, options: Optional[PartialCreateOrderOptions] = None
453
+ self, order_args: OrderArgs, options: PartialCreateOrderOptions | None = None
452
454
  ) -> SignedOrder:
453
455
  """Creates and signs an order."""
454
456
  # add resolve_order_options, or similar
@@ -483,7 +485,7 @@ class PolymarketClobClient:
483
485
 
484
486
  def post_order(
485
487
  self, order: SignedOrder, order_type: OrderType = OrderType.GTC
486
- ) -> Optional[OrderPostResponse]:
488
+ ) -> OrderPostResponse | None:
487
489
  """Posts a SignedOrder."""
488
490
  body = order_to_json(order, self.creds.key, order_type)
489
491
  headers = create_level_2_headers(
@@ -505,18 +507,19 @@ class PolymarketClobClient:
505
507
  logger.warning(msg)
506
508
  error_json = exc.response.json()
507
509
  print("Details:", error_json["error"])
510
+ return None
508
511
 
509
512
  def create_and_post_order(
510
513
  self,
511
514
  order_args: OrderArgs,
512
- options: Optional[PartialCreateOrderOptions] = None,
515
+ options: PartialCreateOrderOptions | None = None,
513
516
  order_type: OrderType = OrderType.GTC,
514
517
  ) -> OrderPostResponse | None:
515
518
  """Utility function to create and publish an order."""
516
519
  order = self.create_order(order_args, options)
517
520
  return self.post_order(order=order, order_type=order_type)
518
521
 
519
- def post_orders(self, args: list[PostOrdersArgs]):
522
+ def post_orders(self, args: list[PostOrdersArgs]) -> list[OrderPostResponse] | None:
520
523
  """Posts multiple SignedOrders at once."""
521
524
  body = [
522
525
  order_to_json(arg.order, self.creds.key, arg.order_type) for arg in args
@@ -549,12 +552,13 @@ class PolymarketClobClient:
549
552
  logger.warning(msg)
550
553
  error_json = exc.response.json()
551
554
  print("Details:", error_json["error"])
555
+ return None
552
556
  else:
553
557
  return order_responses
554
558
 
555
559
  def create_and_post_orders(
556
560
  self, args: list[OrderArgs], order_types: list[OrderType]
557
- ) -> list[OrderPostResponse]:
561
+ ) -> list[OrderPostResponse] | None:
558
562
  """Utility function to create and publish multiple orders at once."""
559
563
  return self.post_orders(
560
564
  [
@@ -597,7 +601,7 @@ class PolymarketClobClient:
597
601
  def create_market_order(
598
602
  self,
599
603
  order_args: MarketOrderArgs,
600
- options: Optional[PartialCreateOrderOptions] = None,
604
+ options: PartialCreateOrderOptions | None = None,
601
605
  ):
602
606
  """Creates and signs a market order."""
603
607
  tick_size = self.__resolve_tick_size(
@@ -640,7 +644,7 @@ class PolymarketClobClient:
640
644
  def create_and_post_market_order(
641
645
  self,
642
646
  order_args: MarketOrderArgs,
643
- options: Optional[PartialCreateOrderOptions] = None,
647
+ options: PartialCreateOrderOptions | None = None,
644
648
  order_type: OrderType = OrderType.FOK,
645
649
  ) -> OrderPostResponse | None:
646
650
  """Utility function to create and publish a market order."""
@@ -678,7 +682,7 @@ class PolymarketClobClient:
678
682
  "DELETE",
679
683
  self._build_url(CANCEL_ORDERS),
680
684
  headers=headers,
681
- data=json.dumps(body).encode("utf-8"),
685
+ content=json.dumps(body).encode("utf-8"),
682
686
  )
683
687
  response.raise_for_status()
684
688
  return OrderCancelResponse(**response.json())
@@ -739,12 +743,12 @@ class PolymarketClobClient:
739
743
 
740
744
  def get_trades(
741
745
  self,
742
- condition_id: Optional[Keccak256] = None,
743
- token_id: Optional[str] = None,
744
- trade_id: Optional[str] = None,
745
- before: Optional[datetime] = None,
746
- after: Optional[datetime] = None,
747
- proxy_address: Optional[int] = None,
746
+ condition_id: Keccak256 | None = None,
747
+ token_id: str | None = None,
748
+ trade_id: str | None = None,
749
+ before: datetime | None = None,
750
+ after: datetime | None = None,
751
+ address: EthAddress | None = None,
748
752
  next_cursor="MA==",
749
753
  ) -> list[PolygonTrade]:
750
754
  """Fetches the trade history for a user."""
@@ -759,8 +763,8 @@ class PolymarketClobClient:
759
763
  params["before"] = int(before.replace(microsecond=0).timestamp())
760
764
  if after:
761
765
  params["after"] = int(after.replace(microsecond=0).timestamp())
762
- if proxy_address:
763
- params["maker_address"] = proxy_address
766
+ if address:
767
+ params["maker_address"] = address
764
768
 
765
769
  request_args = RequestArgs(method="GET", request_path=TRADES)
766
770
  headers = create_level_2_headers(self.signer, self.creds, request_args)
@@ -778,7 +782,7 @@ class PolymarketClobClient:
778
782
 
779
783
  return results
780
784
 
781
- def get_total_rewards(self, date: Optional[datetime] = None) -> DailyEarnedReward:
785
+ def get_total_rewards(self, date: datetime | None = None) -> DailyEarnedReward:
782
786
  """Get the total rewards earned on a given date (seems to only hold the 6 most recent data points)."""
783
787
  if date is None:
784
788
  date = datetime.now(UTC)
@@ -800,27 +804,26 @@ class PolymarketClobClient:
800
804
  return DailyEarnedReward(
801
805
  date=date,
802
806
  asset_address="0x2791Bca1f2de4661ED88A30C99A7a9449Aa84174",
803
- maker_address=self.proxy_address,
807
+ maker_address=self.address,
804
808
  earnings=0.0,
805
809
  asset_rate=0.0,
806
810
  )
807
811
 
808
812
  def get_reward_markets(
809
813
  self,
810
- query: Optional[str] = None,
811
- sort_by: Optional[
812
- Literal[
813
- "market",
814
- "max_spread",
815
- "min_size",
816
- "rate_per_day",
817
- "spread",
818
- "price",
819
- "earnings",
820
- "earning_percentage",
821
- ]
822
- ] = "market",
823
- sort_direction: Optional[Literal["ASC", "DESC"]] = None,
814
+ query: str | None = None,
815
+ sort_by: Literal[
816
+ "market",
817
+ "max_spread",
818
+ "min_size",
819
+ "rate_per_day",
820
+ "spread",
821
+ "price",
822
+ "earnings",
823
+ "earning_percentage",
824
+ ]
825
+ | None = "market",
826
+ sort_direction: Literal["ASC", "DESC"] | None = None,
824
827
  show_favorites: bool = False,
825
828
  ) -> list[RewardMarket]:
826
829
  """
@@ -837,7 +840,7 @@ class PolymarketClobClient:
837
840
  """
838
841
  results = []
839
842
  desc = {"ASC": False, "DESC": True}
840
- params = {
843
+ params: dict[str, bool | str] = {
841
844
  "authenticationType": "magic",
842
845
  "showFavorites": show_favorites,
843
846
  }
@@ -4,10 +4,12 @@ from urllib.parse import urljoin
4
4
 
5
5
  import httpx
6
6
 
7
+ from ..clients.graphql_client import PolymarketGraphQLClient
7
8
  from ..types.common import EthAddress, TimeseriesPoint
8
9
  from ..types.data_types import (
9
10
  Activity,
10
11
  EventLiveVolume,
12
+ GQLPosition,
11
13
  HolderResponse,
12
14
  MarketValue,
13
15
  Position,
@@ -22,6 +24,9 @@ class PolymarketDataClient:
22
24
  def __init__(self, base_url: str = "https://data-api.polymarket.com"):
23
25
  self.base_url = base_url
24
26
  self.client = httpx.Client(http2=True, timeout=30.0)
27
+ self.gql_positions_client = PolymarketGraphQLClient(
28
+ endpoint_name="positions_subgraph"
29
+ )
25
30
 
26
31
  def _build_url(self, endpoint: str) -> str:
27
32
  return urljoin(self.base_url, endpoint)
@@ -31,6 +36,35 @@ class PolymarketDataClient:
31
36
  response.raise_for_status()
32
37
  return response.json()["data"]
33
38
 
39
+ def get_all_positions(
40
+ self,
41
+ user: EthAddress,
42
+ size_threshold: float = 0.0,
43
+ ):
44
+ # data-api /positions endpoint does not support fetching all positions without filters
45
+ # a workaround is to use the GraphQL positions subgraph directly
46
+ query = f"""query {{
47
+ userBalances(where: {{
48
+ user: "{user.lower()}",
49
+ balance_gt: "{int(size_threshold * 10**6)}"
50
+ }}) {{
51
+ user
52
+ asset {{
53
+ id
54
+ condition {{
55
+ id
56
+ }}
57
+ complement
58
+ outcomeIndex
59
+ }}
60
+ balance
61
+ }}
62
+ }}
63
+ """
64
+
65
+ response = self.gql_positions_client.query(query)
66
+ return [GQLPosition(**pos) for pos in response["userBalances"]]
67
+
34
68
  def get_positions(
35
69
  self,
36
70
  user: EthAddress,
@@ -188,7 +222,11 @@ class PolymarketDataClient:
188
222
  min_balance: int = 1,
189
223
  ) -> list[HolderResponse]:
190
224
  """Takes in a condition_id and returns top holders for each corresponding token_id."""
191
- params = {"market": condition_id, "limit": limit, "min_balance": min_balance}
225
+ params: dict[str, int | str] = {
226
+ "market": condition_id,
227
+ "limit": limit,
228
+ "min_balance": min_balance,
229
+ }
192
230
  response = self.client.get(self._build_url("/holders"), params=params)
193
231
  response.raise_for_status()
194
232
  return [HolderResponse(**holder_data) for holder_data in response.json()]
@@ -298,7 +336,7 @@ class PolymarketDataClient:
298
336
  window: Literal["1d", "7d", "30d", "all"] = "all",
299
337
  ):
300
338
  """Get a user's overall profit or volume in the last day, week, month or all."""
301
- params = {
339
+ params: dict[str, int | str] = {
302
340
  "address": user,
303
341
  "window": window,
304
342
  "limit": 1,
@@ -104,29 +104,29 @@ class PolymarketGammaClient:
104
104
 
105
105
  def get_markets(
106
106
  self,
107
- limit: Optional[int] = None,
108
- offset: Optional[int] = None,
109
- order: Optional[str] = None,
107
+ limit: int | None = None,
108
+ offset: int | None = None,
109
+ order: str | None = None,
110
110
  ascending: bool = True,
111
- archived: Optional[bool] = None,
112
- active: Optional[bool] = None,
113
- closed: Optional[bool] = None,
114
- slugs: Optional[list[str]] = None,
115
- market_ids: Optional[list[int]] = None,
116
- token_ids: Optional[list[str]] = None,
117
- condition_ids: Optional[list[str]] = None,
118
- tag_id: Optional[int] = None,
119
- related_tags: Optional[bool] = False,
120
- liquidity_num_min: Optional[float] = None,
121
- liquidity_num_max: Optional[float] = None,
122
- volume_num_min: Optional[float] = None,
123
- volume_num_max: Optional[float] = None,
124
- start_date_min: Optional[datetime] = None,
125
- start_date_max: Optional[datetime] = None,
126
- end_date_min: Optional[datetime] = None,
127
- end_date_max: Optional[datetime] = None,
111
+ archived: bool | None = None,
112
+ active: bool | None = None,
113
+ closed: bool | None = None,
114
+ slugs: list[str] | None = None,
115
+ market_ids: list[int] | None = None,
116
+ token_ids: list[str] | None = None,
117
+ condition_ids: list[str] | None = None,
118
+ tag_id: int | None = None,
119
+ related_tags: bool | None = False,
120
+ liquidity_num_min: float | None = None,
121
+ liquidity_num_max: float | None = None,
122
+ volume_num_min: float | None = None,
123
+ volume_num_max: float | None = None,
124
+ start_date_min: datetime | None = None,
125
+ start_date_max: datetime | None = None,
126
+ end_date_min: datetime | None = None,
127
+ end_date_max: datetime | None = None,
128
128
  ) -> list[GammaMarket]:
129
- params = {}
129
+ params: dict[str, float | int | list[int] | str | list[str] | bool] = {}
130
130
  if limit:
131
131
  params["limit"] = limit
132
132
  if offset: