prediction-market-agent-tooling 0.66.5__py3-none-any.whl → 0.67.1__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.
Files changed (28) hide show
  1. prediction_market_agent_tooling/deploy/agent.py +23 -5
  2. prediction_market_agent_tooling/markets/agent_market.py +9 -2
  3. prediction_market_agent_tooling/markets/manifold/manifold.py +3 -2
  4. prediction_market_agent_tooling/markets/markets.py +5 -5
  5. prediction_market_agent_tooling/markets/metaculus/metaculus.py +3 -1
  6. prediction_market_agent_tooling/markets/omen/omen.py +5 -2
  7. prediction_market_agent_tooling/markets/polymarket/api.py +87 -104
  8. prediction_market_agent_tooling/markets/polymarket/data_models.py +60 -14
  9. prediction_market_agent_tooling/markets/polymarket/data_models_web.py +0 -54
  10. prediction_market_agent_tooling/markets/polymarket/polymarket.py +109 -26
  11. prediction_market_agent_tooling/markets/polymarket/polymarket_subgraph_handler.py +49 -0
  12. prediction_market_agent_tooling/markets/polymarket/utils.py +0 -21
  13. prediction_market_agent_tooling/markets/seer/price_manager.py +65 -46
  14. prediction_market_agent_tooling/markets/seer/seer.py +70 -90
  15. prediction_market_agent_tooling/markets/seer/seer_subgraph_handler.py +48 -35
  16. prediction_market_agent_tooling/markets/seer/swap_pool_handler.py +11 -1
  17. prediction_market_agent_tooling/tools/cow/cow_order.py +26 -11
  18. prediction_market_agent_tooling/tools/cow/models.py +4 -2
  19. prediction_market_agent_tooling/tools/httpx_cached_client.py +13 -6
  20. prediction_market_agent_tooling/tools/tokens/auto_deposit.py +7 -0
  21. prediction_market_agent_tooling/tools/tokens/auto_withdraw.py +8 -0
  22. prediction_market_agent_tooling/tools/tokens/slippage.py +21 -0
  23. prediction_market_agent_tooling/tools/utils.py +5 -2
  24. {prediction_market_agent_tooling-0.66.5.dist-info → prediction_market_agent_tooling-0.67.1.dist-info}/METADATA +1 -1
  25. {prediction_market_agent_tooling-0.66.5.dist-info → prediction_market_agent_tooling-0.67.1.dist-info}/RECORD +28 -26
  26. {prediction_market_agent_tooling-0.66.5.dist-info → prediction_market_agent_tooling-0.67.1.dist-info}/LICENSE +0 -0
  27. {prediction_market_agent_tooling-0.66.5.dist-info → prediction_market_agent_tooling-0.67.1.dist-info}/WHEEL +0 -0
  28. {prediction_market_agent_tooling-0.66.5.dist-info → prediction_market_agent_tooling-0.67.1.dist-info}/entry_points.txt +0 -0
@@ -19,9 +19,11 @@ from prediction_market_agent_tooling.deploy.trade_interval import (
19
19
  )
20
20
  from prediction_market_agent_tooling.gtypes import USD, OutcomeToken, xDai
21
21
  from prediction_market_agent_tooling.loggers import logger
22
+ from prediction_market_agent_tooling.markets.agent_market import AgentMarket, FilterBy
23
+ from prediction_market_agent_tooling.markets.agent_market import (
24
+ MarketType as AgentMarketType,
25
+ )
22
26
  from prediction_market_agent_tooling.markets.agent_market import (
23
- AgentMarket,
24
- FilterBy,
25
27
  ProcessedMarket,
26
28
  ProcessedTradedMarket,
27
29
  SortBy,
@@ -335,6 +337,15 @@ class DeployablePredictionAgent(DeployableAgent):
335
337
  return True
336
338
  return False
337
339
 
340
+ @property
341
+ def agent_market_type(self) -> AgentMarketType:
342
+ if self.fetch_scalar_markets:
343
+ return AgentMarketType.SCALAR
344
+ elif self.fetch_categorical_markets:
345
+ return AgentMarketType.CATEGORICAL
346
+ else:
347
+ return AgentMarketType.BINARY
348
+
338
349
  def get_markets(
339
350
  self,
340
351
  market_type: MarketType,
@@ -343,14 +354,16 @@ class DeployablePredictionAgent(DeployableAgent):
343
354
  Override this method to customize what markets will fetch for processing.
344
355
  """
345
356
  cls = market_type.market_class
357
+
358
+ agent_market_type = self.agent_market_type
359
+
346
360
  # Fetch the soonest closing markets to choose from
347
361
  available_markets = cls.get_markets(
348
362
  limit=self.n_markets_to_fetch,
349
363
  sort_by=self.get_markets_sort_by,
350
364
  filter_by=self.get_markets_filter_by,
351
365
  created_after=self.trade_on_markets_created_after,
352
- fetch_categorical_markets=self.fetch_categorical_markets,
353
- fetch_scalar_markets=self.fetch_scalar_markets,
366
+ market_type=agent_market_type,
354
367
  )
355
368
  return available_markets
356
369
 
@@ -628,7 +641,12 @@ class DeployableTraderAgent(DeployablePredictionAgent):
628
641
  api_keys = APIKeys()
629
642
  user_id = market.get_user_id(api_keys=api_keys)
630
643
 
631
- existing_position = market.get_position(user_id=user_id)
644
+ try:
645
+ existing_position = market.get_position(user_id=user_id)
646
+ except Exception as e:
647
+ logger.warning(f"Could not get position for user {user_id}, exception {e}")
648
+ return None
649
+
632
650
  trades = self.build_trades(
633
651
  market=market,
634
652
  answer=processed_market.answer,
@@ -64,6 +64,13 @@ class FilterBy(str, Enum):
64
64
  NONE = "none"
65
65
 
66
66
 
67
+ class MarketType(str, Enum):
68
+ ALL = "all"
69
+ CATEGORICAL = "categorical"
70
+ SCALAR = "scalar"
71
+ BINARY = "binary"
72
+
73
+
67
74
  class AgentMarket(BaseModel):
68
75
  """
69
76
  Common market class that can be created from vendor specific markets.
@@ -369,8 +376,8 @@ class AgentMarket(BaseModel):
369
376
  filter_by: FilterBy = FilterBy.OPEN,
370
377
  created_after: t.Optional[DatetimeUTC] = None,
371
378
  excluded_questions: set[str] | None = None,
372
- fetch_categorical_markets: bool = False,
373
- fetch_scalar_markets: bool = False,
379
+ market_type: MarketType = MarketType.ALL,
380
+ include_conditional_markets: bool = False,
374
381
  ) -> t.Sequence["AgentMarket"]:
375
382
  raise NotImplementedError("Subclasses must implement this method")
376
383
 
@@ -12,6 +12,7 @@ from prediction_market_agent_tooling.markets.agent_market import (
12
12
  AgentMarket,
13
13
  FilterBy,
14
14
  MarketFees,
15
+ MarketType,
15
16
  SortBy,
16
17
  )
17
18
  from prediction_market_agent_tooling.markets.manifold.api import (
@@ -110,8 +111,8 @@ class ManifoldAgentMarket(AgentMarket):
110
111
  filter_by: FilterBy = FilterBy.OPEN,
111
112
  created_after: t.Optional[DatetimeUTC] = None,
112
113
  excluded_questions: set[str] | None = None,
113
- fetch_categorical_markets: bool = False,
114
- fetch_scalar_markets: bool = False,
114
+ market_type: MarketType = MarketType.ALL,
115
+ include_conditional_markets: bool = False,
115
116
  ) -> t.Sequence["ManifoldAgentMarket"]:
116
117
  sort: t.Literal["newest", "close-date"] | None
117
118
  if sort_by == SortBy.CLOSING_SOONEST:
@@ -3,11 +3,11 @@ from enum import Enum
3
3
 
4
4
  from prediction_market_agent_tooling.jobs.jobs_models import JobAgentMarket
5
5
  from prediction_market_agent_tooling.jobs.omen.omen_jobs import OmenJobAgentMarket
6
+ from prediction_market_agent_tooling.markets.agent_market import AgentMarket, FilterBy
6
7
  from prediction_market_agent_tooling.markets.agent_market import (
7
- AgentMarket,
8
- FilterBy,
9
- SortBy,
8
+ MarketType as AgentMarketType,
10
9
  )
10
+ from prediction_market_agent_tooling.markets.agent_market import SortBy
11
11
  from prediction_market_agent_tooling.markets.manifold.manifold import (
12
12
  ManifoldAgentMarket,
13
13
  )
@@ -68,7 +68,7 @@ def get_binary_markets(
68
68
  sort_by: SortBy = SortBy.NONE,
69
69
  excluded_questions: set[str] | None = None,
70
70
  created_after: DatetimeUTC | None = None,
71
- fetch_scalar_markets: bool = False,
71
+ agent_market_type: AgentMarketType = AgentMarketType.BINARY,
72
72
  ) -> t.Sequence[AgentMarket]:
73
73
  agent_market_class = MARKET_TYPE_TO_AGENT_MARKET[market_type]
74
74
  markets = agent_market_class.get_markets(
@@ -77,6 +77,6 @@ def get_binary_markets(
77
77
  filter_by=filter_by,
78
78
  created_after=created_after,
79
79
  excluded_questions=excluded_questions,
80
- fetch_scalar_markets=fetch_scalar_markets,
80
+ market_type=agent_market_type,
81
81
  )
82
82
  return markets
@@ -9,6 +9,7 @@ from prediction_market_agent_tooling.markets.agent_market import (
9
9
  AgentMarket,
10
10
  FilterBy,
11
11
  MarketFees,
12
+ MarketType,
12
13
  ProcessedMarket,
13
14
  SortBy,
14
15
  )
@@ -72,8 +73,9 @@ class MetaculusAgentMarket(AgentMarket):
72
73
  filter_by: FilterBy = FilterBy.OPEN,
73
74
  created_after: t.Optional[DatetimeUTC] = None,
74
75
  excluded_questions: set[str] | None = None,
76
+ market_type: MarketType = MarketType.ALL,
77
+ include_conditional_markets: bool = False,
75
78
  tournament_id: int | None = None,
76
- fetch_scalar_markets: bool = False,
77
79
  ) -> t.Sequence["MetaculusAgentMarket"]:
78
80
  order_by: str | None
79
81
  if sort_by == SortBy.NONE:
@@ -25,6 +25,7 @@ from prediction_market_agent_tooling.markets.agent_market import (
25
25
  AgentMarket,
26
26
  FilterBy,
27
27
  MarketFees,
28
+ MarketType,
28
29
  ProcessedMarket,
29
30
  ProcessedTradedMarket,
30
31
  SortBy,
@@ -379,9 +380,11 @@ class OmenAgentMarket(AgentMarket):
379
380
  filter_by: FilterBy = FilterBy.OPEN,
380
381
  created_after: t.Optional[DatetimeUTC] = None,
381
382
  excluded_questions: set[str] | None = None,
382
- fetch_categorical_markets: bool = False,
383
- fetch_scalar_markets: bool = False,
383
+ market_type: MarketType = MarketType.ALL,
384
+ include_conditional_markets: bool = False,
384
385
  ) -> t.Sequence["OmenAgentMarket"]:
386
+ fetch_categorical_markets = market_type == MarketType.CATEGORICAL
387
+
385
388
  return [
386
389
  OmenAgentMarket.from_data_model(m)
387
390
  for m in OmenSubgraphHandler().get_omen_markets_simple(
@@ -1,140 +1,123 @@
1
1
  import typing as t
2
+ from enum import Enum
3
+ from urllib.parse import urljoin
2
4
 
3
- import requests
5
+ import httpx
4
6
  import tenacity
5
7
 
6
8
  from prediction_market_agent_tooling.loggers import logger
7
9
  from prediction_market_agent_tooling.markets.polymarket.data_models import (
8
10
  POLYMARKET_FALSE_OUTCOME,
9
11
  POLYMARKET_TRUE_OUTCOME,
10
- MarketsEndpointResponse,
11
- PolymarketMarket,
12
- PolymarketMarketWithPrices,
13
- PolymarketPriceResponse,
14
- PolymarketTokenWithPrices,
15
- Prices,
12
+ PolymarketGammaResponse,
13
+ PolymarketGammaResponseDataItem,
16
14
  )
15
+ from prediction_market_agent_tooling.tools.datetime_utc import DatetimeUTC
16
+ from prediction_market_agent_tooling.tools.httpx_cached_client import HttpxCachedClient
17
17
  from prediction_market_agent_tooling.tools.utils import response_to_model
18
18
 
19
- POLYMARKET_API_BASE_URL = "https://clob.polymarket.com/"
20
19
  MARKETS_LIMIT = 100 # Polymarket will only return up to 100 markets
20
+ POLYMARKET_GAMMA_API_BASE_URL = "https://gamma-api.polymarket.com/"
21
+
22
+
23
+ class PolymarketOrderByEnum(str, Enum):
24
+ LIQUIDITY = "liquidity"
25
+ START_DATE = "startDate"
26
+ END_DATE = "endDate"
27
+ VOLUME_24HR = "volume24hr"
21
28
 
22
29
 
23
30
  @tenacity.retry(
24
- stop=tenacity.stop_after_attempt(5),
25
- wait=tenacity.wait_chain(*[tenacity.wait_fixed(n) for n in range(1, 6)]),
26
- after=lambda x: logger.debug(f"get_polymarkets failed, {x.attempt_number=}."),
31
+ stop=tenacity.stop_after_attempt(2),
32
+ wait=tenacity.wait_fixed(1),
33
+ after=lambda x: logger.debug(
34
+ f"get_polymarkets_with_pagination failed, {x.attempt_number=}."
35
+ ),
27
36
  )
28
- def get_polymarkets(
29
- limit: int,
30
- with_rewards: bool = False,
31
- next_cursor: str | None = None,
32
- ) -> MarketsEndpointResponse:
33
- url = (
34
- f"{POLYMARKET_API_BASE_URL}/{'sampling-markets' if with_rewards else 'markets'}"
35
- )
36
- params: dict[str, str | int | float | None] = {
37
- "limit": min(limit, MARKETS_LIMIT),
38
- }
39
- if next_cursor is not None:
40
- params["next_cursor"] = next_cursor
41
- return response_to_model(requests.get(url, params=params), MarketsEndpointResponse)
42
-
43
-
44
- def get_polymarket_binary_markets(
37
+ def get_polymarkets_with_pagination(
45
38
  limit: int,
46
- closed: bool | None = False,
39
+ created_after: t.Optional[DatetimeUTC] = None,
40
+ active: bool | None = None,
41
+ closed: bool | None = None,
47
42
  excluded_questions: set[str] | None = None,
48
- with_rewards: bool = False,
49
- main_markets_only: bool = True,
50
- ) -> list[PolymarketMarketWithPrices]:
43
+ only_binary: bool = True,
44
+ archived: bool = False,
45
+ ascending: bool = False,
46
+ order_by: PolymarketOrderByEnum = PolymarketOrderByEnum.VOLUME_24HR,
47
+ ) -> list[PolymarketGammaResponseDataItem]:
51
48
  """
52
- See https://learn.polymarket.com/trading-rewards for information about rewards.
49
+ Binary markets have len(model.markets) == 1.
50
+ Categorical markets have len(model.markets) > 1
53
51
  """
54
-
55
- all_markets: list[PolymarketMarketWithPrices] = []
56
- next_cursor: str | None = None
57
-
58
- while True:
59
- response = get_polymarkets(
60
- limit, with_rewards=with_rewards, next_cursor=next_cursor
52
+ client: httpx.Client = HttpxCachedClient(ttl=60).get_client()
53
+ all_markets: list[PolymarketGammaResponseDataItem] = []
54
+ offset = 0
55
+ remaining = limit
56
+
57
+ while remaining > 0:
58
+ # Calculate how many items to request in this batch (up to MARKETS_LIMIT or remaining)
59
+ # By default we fetch many markets because not possible to filter by binary/categorical
60
+ batch_size = MARKETS_LIMIT
61
+
62
+ # Build query parameters, excluding None values
63
+ params = {
64
+ "limit": batch_size,
65
+ "active": str(active).lower() if active is not None else None,
66
+ "archived": str(archived).lower(),
67
+ "closed": str(closed).lower() if closed is not None else None,
68
+ "order": order_by.value,
69
+ "ascending": str(ascending).lower(),
70
+ "offset": offset,
71
+ }
72
+ params_not_none = {k: v for k, v in params.items() if v is not None}
73
+ url = urljoin(
74
+ POLYMARKET_GAMMA_API_BASE_URL,
75
+ f"events/pagination",
61
76
  )
62
77
 
63
- for market in response.data:
64
- # Closed markets means resolved markets.
65
- if closed is not None and market.closed != closed:
66
- continue
67
-
68
- # Skip markets that are inactive.
69
- # Documentation does not provide more details about this, but if API returns them, website gives "Oops...we didn't forecast this".
70
- if not market.active:
71
- continue
78
+ r = client.get(url, params=params_not_none)
79
+ r.raise_for_status()
72
80
 
73
- # Skip also those that were archived.
74
- # Again nothing about it in documentation and API doesn't seem to return them, but to be safe.
75
- if market.archived:
76
- continue
81
+ market_response = response_to_model(r, PolymarketGammaResponse)
77
82
 
78
- if excluded_questions and market.question in excluded_questions:
83
+ markets_to_add = []
84
+ for m in market_response.data:
85
+ if excluded_questions and m.title in excluded_questions:
79
86
  continue
80
87
 
81
- # Atm we work with binary markets only.
82
- if sorted(token.outcome for token in market.tokens) != [
83
- POLYMARKET_FALSE_OUTCOME,
84
- POLYMARKET_TRUE_OUTCOME,
85
- ]:
86
- continue
88
+ sorted_outcome_list = sorted(m.markets[0].outcomes_list)
89
+ if only_binary:
90
+ # We keep markets that are only Yes,No
91
+ if len(m.markets) > 1 or sorted_outcome_list != [
92
+ POLYMARKET_FALSE_OUTCOME,
93
+ POLYMARKET_TRUE_OUTCOME,
94
+ ]:
95
+ continue
87
96
 
88
- # This is pretty slow to do here, but our safest option at the moment. So keep it as the last filter.
89
- # TODO: Add support for `description` for `AgentMarket` and if it isn't None, use it in addition to the question in all agents. Then this can be removed.
90
- if main_markets_only and not market.fetch_if_its_a_main_market():
97
+ if created_after and created_after > m.startDate:
91
98
  continue
92
99
 
93
- tokens_with_price = get_market_tokens_with_prices(market)
94
- market_with_prices = PolymarketMarketWithPrices.model_validate(
95
- {**market.model_dump(), "tokens": tokens_with_price}
96
- )
100
+ markets_to_add.append(m)
97
101
 
98
- all_markets.append(market_with_prices)
102
+ if only_binary:
103
+ markets_to_add = [
104
+ market for market in market_response.data if len(market.markets) == 1
105
+ ]
99
106
 
100
- if len(all_markets) >= limit:
101
- break
107
+ # Add the markets from this batch to our results
108
+ all_markets.extend(markets_to_add)
102
109
 
103
- next_cursor = response.next_cursor
110
+ # Update counters
111
+ offset += len(market_response.data)
112
+ remaining -= len(markets_to_add)
104
113
 
105
- if next_cursor == "LTE=":
106
- # 'LTE=' means the end.
114
+ # Stop if we've reached our limit or there are no more results
115
+ if (
116
+ remaining <= 0
117
+ or not market_response.pagination.hasMore
118
+ or len(market_response.data) == 0
119
+ ):
107
120
  break
108
121
 
122
+ # Return exactly the number of items requested (in case we got more due to batch size)
109
123
  return all_markets[:limit]
110
-
111
-
112
- def get_polymarket_market(condition_id: str) -> PolymarketMarket:
113
- url = f"{POLYMARKET_API_BASE_URL}/markets/{condition_id}"
114
- return response_to_model(requests.get(url), PolymarketMarket)
115
-
116
-
117
- def get_token_price(
118
- token_id: str, side: t.Literal["buy", "sell"]
119
- ) -> PolymarketPriceResponse:
120
- url = f"{POLYMARKET_API_BASE_URL}/price"
121
- params = {"token_id": token_id, "side": side}
122
- return response_to_model(requests.get(url, params=params), PolymarketPriceResponse)
123
-
124
-
125
- def get_market_tokens_with_prices(
126
- market: PolymarketMarket,
127
- ) -> list[PolymarketTokenWithPrices]:
128
- tokens_with_prices = [
129
- PolymarketTokenWithPrices(
130
- token_id=token.token_id,
131
- outcome=token.outcome,
132
- winner=token.winner,
133
- prices=Prices(
134
- BUY=get_token_price(token.token_id, "buy").price_dec,
135
- SELL=get_token_price(token.token_id, "sell").price_dec,
136
- ),
137
- )
138
- for token in market.tokens
139
- ]
140
- return tokens_with_prices
@@ -1,3 +1,5 @@
1
+ import json
2
+
1
3
  from pydantic import BaseModel
2
4
 
3
5
  from prediction_market_agent_tooling.gtypes import USDC, OutcomeStr, Probability
@@ -5,9 +7,9 @@ from prediction_market_agent_tooling.markets.data_models import Resolution
5
7
  from prediction_market_agent_tooling.markets.polymarket.data_models_web import (
6
8
  POLYMARKET_FALSE_OUTCOME,
7
9
  POLYMARKET_TRUE_OUTCOME,
8
- PolymarketFullMarket,
9
10
  construct_polymarket_url,
10
11
  )
12
+ from prediction_market_agent_tooling.tools.hexbytes_custom import HexBytes
11
13
  from prediction_market_agent_tooling.tools.utils import DatetimeUTC
12
14
 
13
15
 
@@ -26,6 +28,63 @@ class PolymarketToken(BaseModel):
26
28
  winner: bool
27
29
 
28
30
 
31
+ class PolymarketGammaMarket(BaseModel):
32
+ conditionId: HexBytes
33
+ outcomes: str
34
+ outcomePrices: str | None = None
35
+ marketMakerAddress: str
36
+ createdAt: DatetimeUTC
37
+ updatedAt: DatetimeUTC | None = None
38
+ archived: bool
39
+ questionId: str | None = None
40
+ clobTokenIds: str | None = None # int-encoded hex
41
+
42
+ @property
43
+ def outcomes_list(self) -> list[OutcomeStr]:
44
+ return [OutcomeStr(i) for i in json.loads(self.outcomes)]
45
+
46
+ @property
47
+ def outcome_prices(self) -> list[float] | None:
48
+ if not self.outcomePrices:
49
+ return None
50
+ return [float(i) for i in json.loads(self.outcomePrices)]
51
+
52
+
53
+ class PolymarketGammaTag(BaseModel):
54
+ label: str
55
+ slug: str
56
+
57
+
58
+ class PolymarketGammaResponseDataItem(BaseModel):
59
+ id: str
60
+ slug: str
61
+ volume: float | None = None
62
+ startDate: DatetimeUTC
63
+ endDate: DatetimeUTC | None = None
64
+ liquidity: float | None = None
65
+ liquidityClob: float | None = None
66
+ title: str
67
+ description: str
68
+ archived: bool
69
+ closed: bool
70
+ active: bool
71
+ markets: list[PolymarketGammaMarket]
72
+ tags: list[PolymarketGammaTag]
73
+
74
+ @property
75
+ def url(self) -> str:
76
+ return construct_polymarket_url(self.slug)
77
+
78
+
79
+ class PolymarketGammaPagination(BaseModel):
80
+ hasMore: bool
81
+
82
+
83
+ class PolymarketGammaResponse(BaseModel):
84
+ data: list[PolymarketGammaResponseDataItem]
85
+ pagination: PolymarketGammaPagination
86
+
87
+
29
88
  class PolymarketMarket(BaseModel):
30
89
  enable_order_book: bool
31
90
  active: bool
@@ -89,19 +148,6 @@ class PolymarketMarket(BaseModel):
89
148
  f"Should not happen, invalid winner tokens: {winner_tokens}"
90
149
  )
91
150
 
92
- def fetch_full_market(self) -> PolymarketFullMarket | None:
93
- return PolymarketFullMarket.fetch_from_url(self.url)
94
-
95
- def fetch_if_its_a_main_market(self) -> bool:
96
- # On Polymarket, there are markets that are actually a group of multiple Yes/No markets, for example https://polymarket.com/event/presidential-election-winner-2024.
97
- # But API returns them individually, and then we receive questions such as "Will any other Republican Politician win the 2024 US Presidential Election?",
98
- # which are naturally unpredictable without futher details.
99
- # This is a heuristic to filter them out.
100
- # Warning: This is a very slow operation, as it requires fetching the website. Use it only when necessary.
101
- full_market = self.fetch_full_market()
102
- # `full_market` can be None, if this class come from a multiple Yes/No market, becase then, the constructed URL is invalid (and there is now way to construct an valid one from the data we have).
103
- return full_market is not None and full_market.is_main_market
104
-
105
151
 
106
152
  class MarketsEndpointResponse(BaseModel):
107
153
  limit: int
@@ -5,14 +5,11 @@ Keep in mind that not all fields were used so far, so there might be some bugs.
5
5
  These models are based on what Polymarket's website returns in the response, and `prediction_market_agent_tooling/markets/polymarket/data_models.py` are based on what their API returns.
6
6
  """
7
7
 
8
- import json
9
8
  import typing as t
10
9
 
11
- import requests
12
10
  from pydantic import BaseModel, field_validator
13
11
 
14
12
  from prediction_market_agent_tooling.gtypes import USDC, HexAddress, OutcomeStr
15
- from prediction_market_agent_tooling.loggers import logger
16
13
  from prediction_market_agent_tooling.markets.data_models import Resolution
17
14
  from prediction_market_agent_tooling.tools.utils import DatetimeUTC
18
15
 
@@ -293,57 +290,6 @@ class PolymarketFullMarket(BaseModel):
293
290
  )
294
291
  return self.markets[0]
295
292
 
296
- @staticmethod
297
- def fetch_from_url(url: str) -> "PolymarketFullMarket | None":
298
- """
299
- Get the full market data from the Polymarket website.
300
-
301
- Returns None if this market's url returns "Oops...we didn't forecast this", see `check_if_its_a_main_market` method for more details.
302
-
303
- Warning: This is a very slow operation, as it requires fetching the website. Use it only when necessary.
304
- """
305
- logger.info(f"Fetching full market from {url}")
306
-
307
- # Fetch the website as a normal browser would.
308
- headers = {
309
- "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.3"
310
- }
311
- content = requests.get(url, headers=headers).text
312
-
313
- # Find the JSON with the data within the content.
314
- start_tag = """<script id="__NEXT_DATA__" type="application/json" crossorigin="anonymous">"""
315
- start_idx = content.find(start_tag) + len(start_tag)
316
- end_idx = content.find("</script>", start_idx)
317
- response_data = content[start_idx:end_idx]
318
-
319
- # Parsing.
320
- response_dict = json.loads(response_data)
321
- response_model = PolymarketWebResponse.model_validate(response_dict)
322
-
323
- full_market_queries = [
324
- q
325
- for q in response_model.props.pageProps.dehydratedState.queries
326
- if isinstance(q.state.data, PolymarketFullMarket)
327
- ]
328
-
329
- # We expect either 0 markets (if it doesn't exist) or 1 market.
330
- if len(full_market_queries) not in (0, 1):
331
- raise ValueError(
332
- f"Unexpected number of queries in the response, please check it out and modify the code accordingly: `{response_dict}`"
333
- )
334
-
335
- # It will be `PolymarketFullMarket` thanks to the filter above.
336
- market = (
337
- t.cast(PolymarketFullMarket, full_market_queries[0].state.data)
338
- if full_market_queries
339
- else None
340
- )
341
-
342
- if market is None:
343
- logger.warning(f"No polymarket found for {url}")
344
-
345
- return market
346
-
347
293
 
348
294
  class PriceSide(BaseModel):
349
295
  price: USDC