prediction-market-agent-tooling 0.65.5__py3-none-any.whl → 0.69.17.dev1149__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 (88) hide show
  1. prediction_market_agent_tooling/abis/agentresultmapping.abi.json +192 -0
  2. prediction_market_agent_tooling/abis/erc1155.abi.json +352 -0
  3. prediction_market_agent_tooling/abis/processor.abi.json +16 -0
  4. prediction_market_agent_tooling/abis/swapr_quoter.abi.json +221 -0
  5. prediction_market_agent_tooling/abis/swapr_router.abi.json +634 -0
  6. prediction_market_agent_tooling/benchmark/benchmark.py +1 -1
  7. prediction_market_agent_tooling/benchmark/utils.py +13 -0
  8. prediction_market_agent_tooling/chains.py +1 -0
  9. prediction_market_agent_tooling/config.py +61 -2
  10. prediction_market_agent_tooling/data_download/langfuse_data_downloader.py +405 -0
  11. prediction_market_agent_tooling/deploy/agent.py +199 -67
  12. prediction_market_agent_tooling/deploy/agent_example.py +1 -1
  13. prediction_market_agent_tooling/deploy/betting_strategy.py +412 -68
  14. prediction_market_agent_tooling/deploy/constants.py +6 -0
  15. prediction_market_agent_tooling/gtypes.py +11 -1
  16. prediction_market_agent_tooling/jobs/jobs_models.py +2 -2
  17. prediction_market_agent_tooling/jobs/omen/omen_jobs.py +19 -20
  18. prediction_market_agent_tooling/loggers.py +9 -1
  19. prediction_market_agent_tooling/logprobs_parser.py +2 -1
  20. prediction_market_agent_tooling/markets/agent_market.py +106 -18
  21. prediction_market_agent_tooling/markets/blockchain_utils.py +37 -19
  22. prediction_market_agent_tooling/markets/data_models.py +120 -7
  23. prediction_market_agent_tooling/markets/manifold/data_models.py +5 -3
  24. prediction_market_agent_tooling/markets/manifold/manifold.py +21 -2
  25. prediction_market_agent_tooling/markets/manifold/utils.py +8 -2
  26. prediction_market_agent_tooling/markets/market_type.py +74 -0
  27. prediction_market_agent_tooling/markets/markets.py +7 -99
  28. prediction_market_agent_tooling/markets/metaculus/data_models.py +3 -3
  29. prediction_market_agent_tooling/markets/metaculus/metaculus.py +5 -8
  30. prediction_market_agent_tooling/markets/omen/cow_contracts.py +5 -1
  31. prediction_market_agent_tooling/markets/omen/data_models.py +63 -32
  32. prediction_market_agent_tooling/markets/omen/omen.py +112 -23
  33. prediction_market_agent_tooling/markets/omen/omen_constants.py +8 -0
  34. prediction_market_agent_tooling/markets/omen/omen_contracts.py +18 -203
  35. prediction_market_agent_tooling/markets/omen/omen_resolving.py +33 -13
  36. prediction_market_agent_tooling/markets/omen/omen_subgraph_handler.py +23 -18
  37. prediction_market_agent_tooling/markets/polymarket/api.py +123 -100
  38. prediction_market_agent_tooling/markets/polymarket/clob_manager.py +156 -0
  39. prediction_market_agent_tooling/markets/polymarket/constants.py +15 -0
  40. prediction_market_agent_tooling/markets/polymarket/data_models.py +95 -19
  41. prediction_market_agent_tooling/markets/polymarket/polymarket.py +373 -29
  42. prediction_market_agent_tooling/markets/polymarket/polymarket_contracts.py +35 -0
  43. prediction_market_agent_tooling/markets/polymarket/polymarket_subgraph_handler.py +91 -0
  44. prediction_market_agent_tooling/markets/polymarket/utils.py +1 -22
  45. prediction_market_agent_tooling/markets/seer/data_models.py +111 -17
  46. prediction_market_agent_tooling/markets/seer/exceptions.py +2 -0
  47. prediction_market_agent_tooling/markets/seer/price_manager.py +165 -50
  48. prediction_market_agent_tooling/markets/seer/seer.py +393 -106
  49. prediction_market_agent_tooling/markets/seer/seer_api.py +28 -0
  50. prediction_market_agent_tooling/markets/seer/seer_contracts.py +115 -5
  51. prediction_market_agent_tooling/markets/seer/seer_subgraph_handler.py +297 -66
  52. prediction_market_agent_tooling/markets/seer/subgraph_data_models.py +43 -8
  53. prediction_market_agent_tooling/markets/seer/swap_pool_handler.py +80 -0
  54. prediction_market_agent_tooling/tools/_generic_value.py +8 -2
  55. prediction_market_agent_tooling/tools/betting_strategies/kelly_criterion.py +271 -8
  56. prediction_market_agent_tooling/tools/betting_strategies/utils.py +6 -1
  57. prediction_market_agent_tooling/tools/caches/db_cache.py +219 -117
  58. prediction_market_agent_tooling/tools/caches/serializers.py +11 -2
  59. prediction_market_agent_tooling/tools/contract.py +480 -38
  60. prediction_market_agent_tooling/tools/contract_utils.py +61 -0
  61. prediction_market_agent_tooling/tools/cow/cow_order.py +218 -45
  62. prediction_market_agent_tooling/tools/cow/models.py +122 -0
  63. prediction_market_agent_tooling/tools/cow/semaphore.py +104 -0
  64. prediction_market_agent_tooling/tools/datetime_utc.py +14 -2
  65. prediction_market_agent_tooling/tools/db/db_manager.py +59 -0
  66. prediction_market_agent_tooling/tools/hexbytes_custom.py +4 -1
  67. prediction_market_agent_tooling/tools/httpx_cached_client.py +15 -6
  68. prediction_market_agent_tooling/tools/langfuse_client_utils.py +21 -8
  69. prediction_market_agent_tooling/tools/openai_utils.py +31 -0
  70. prediction_market_agent_tooling/tools/perplexity/perplexity_client.py +86 -0
  71. prediction_market_agent_tooling/tools/perplexity/perplexity_models.py +26 -0
  72. prediction_market_agent_tooling/tools/perplexity/perplexity_search.py +73 -0
  73. prediction_market_agent_tooling/tools/rephrase.py +71 -0
  74. prediction_market_agent_tooling/tools/singleton.py +11 -6
  75. prediction_market_agent_tooling/tools/streamlit_utils.py +188 -0
  76. prediction_market_agent_tooling/tools/tokens/auto_deposit.py +64 -0
  77. prediction_market_agent_tooling/tools/tokens/auto_withdraw.py +8 -0
  78. prediction_market_agent_tooling/tools/tokens/slippage.py +21 -0
  79. prediction_market_agent_tooling/tools/tokens/usd.py +5 -2
  80. prediction_market_agent_tooling/tools/utils.py +61 -3
  81. prediction_market_agent_tooling/tools/web3_utils.py +63 -9
  82. {prediction_market_agent_tooling-0.65.5.dist-info → prediction_market_agent_tooling-0.69.17.dev1149.dist-info}/METADATA +13 -9
  83. {prediction_market_agent_tooling-0.65.5.dist-info → prediction_market_agent_tooling-0.69.17.dev1149.dist-info}/RECORD +86 -64
  84. {prediction_market_agent_tooling-0.65.5.dist-info → prediction_market_agent_tooling-0.69.17.dev1149.dist-info}/WHEEL +1 -1
  85. prediction_market_agent_tooling/abis/omen_agentresultmapping.abi.json +0 -171
  86. prediction_market_agent_tooling/markets/polymarket/data_models_web.py +0 -420
  87. {prediction_market_agent_tooling-0.65.5.dist-info → prediction_market_agent_tooling-0.69.17.dev1149.dist-info}/entry_points.txt +0 -0
  88. {prediction_market_agent_tooling-0.65.5.dist-info → prediction_market_agent_tooling-0.69.17.dev1149.dist-info/licenses}/LICENSE +0 -0
@@ -1,140 +1,163 @@
1
1
  import typing as t
2
+ from datetime import timedelta
3
+ from enum import Enum
4
+ from urllib.parse import urljoin
2
5
 
3
- import requests
6
+ import httpx
4
7
  import tenacity
5
8
 
9
+ from prediction_market_agent_tooling.gtypes import ChecksumAddress, HexBytes
6
10
  from prediction_market_agent_tooling.loggers import logger
7
11
  from prediction_market_agent_tooling.markets.polymarket.data_models import (
8
12
  POLYMARKET_FALSE_OUTCOME,
9
13
  POLYMARKET_TRUE_OUTCOME,
10
- MarketsEndpointResponse,
11
- PolymarketMarket,
12
- PolymarketMarketWithPrices,
13
- PolymarketPriceResponse,
14
- PolymarketTokenWithPrices,
15
- Prices,
14
+ PolymarketGammaResponse,
15
+ PolymarketGammaResponseDataItem,
16
+ PolymarketPositionResponse,
16
17
  )
18
+ from prediction_market_agent_tooling.tools.datetime_utc import DatetimeUTC
19
+ from prediction_market_agent_tooling.tools.httpx_cached_client import HttpxCachedClient
17
20
  from prediction_market_agent_tooling.tools.utils import response_to_model
18
21
 
19
- POLYMARKET_API_BASE_URL = "https://clob.polymarket.com/"
20
22
  MARKETS_LIMIT = 100 # Polymarket will only return up to 100 markets
23
+ POLYMARKET_GAMMA_API_BASE_URL = "https://gamma-api.polymarket.com/"
21
24
 
22
25
 
23
- @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=}."),
27
- )
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)
26
+ class PolymarketOrderByEnum(str, Enum):
27
+ LIQUIDITY = "liquidity"
28
+ START_DATE = "startDate"
29
+ END_DATE = "endDate"
30
+ VOLUME_24HR = "volume24hr"
42
31
 
43
32
 
44
- def get_polymarket_binary_markets(
33
+ @tenacity.retry(
34
+ stop=tenacity.stop_after_attempt(2),
35
+ wait=tenacity.wait_fixed(1),
36
+ after=lambda x: logger.debug(
37
+ f"get_polymarkets_with_pagination failed, {x.attempt_number=}."
38
+ ),
39
+ )
40
+ def get_polymarkets_with_pagination(
45
41
  limit: int,
46
- closed: bool | None = False,
42
+ created_after: t.Optional[DatetimeUTC] = None,
43
+ active: bool | None = None,
44
+ closed: bool | None = None,
47
45
  excluded_questions: set[str] | None = None,
48
- with_rewards: bool = False,
49
- main_markets_only: bool = True,
50
- ) -> list[PolymarketMarketWithPrices]:
46
+ only_binary: bool = True,
47
+ archived: bool = False,
48
+ ascending: bool = False,
49
+ order_by: PolymarketOrderByEnum = PolymarketOrderByEnum.VOLUME_24HR,
50
+ ) -> list[PolymarketGammaResponseDataItem]:
51
51
  """
52
- See https://learn.polymarket.com/trading-rewards for information about rewards.
52
+ Binary markets have len(model.markets) == 1.
53
+ Categorical markets have len(model.markets) > 1
53
54
  """
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
55
+ client: httpx.Client = HttpxCachedClient(ttl=timedelta(seconds=60)).get_client()
56
+ all_markets: list[PolymarketGammaResponseDataItem] = []
57
+ offset = 0
58
+ remaining = limit
59
+
60
+ while remaining > 0:
61
+ # Calculate how many items to request in this batch (up to MARKETS_LIMIT or remaining)
62
+ # By default we fetch many markets because not possible to filter by binary/categorical
63
+ batch_size = MARKETS_LIMIT
64
+
65
+ # Build query parameters, excluding None values
66
+ params = {
67
+ "limit": batch_size,
68
+ "active": str(active).lower() if active is not None else None,
69
+ "archived": str(archived).lower(),
70
+ "closed": str(closed).lower() if closed is not None else None,
71
+ "order": order_by.value,
72
+ "ascending": str(ascending).lower(),
73
+ "offset": offset,
74
+ }
75
+ params_not_none = {k: v for k, v in params.items() if v is not None}
76
+ url = urljoin(
77
+ POLYMARKET_GAMMA_API_BASE_URL,
78
+ f"events/pagination",
61
79
  )
62
80
 
63
- for market in response.data:
64
- # Closed markets means resolved markets.
65
- if closed is not None and market.closed != closed:
66
- continue
81
+ r = client.get(url, params=params_not_none)
82
+ r.raise_for_status()
67
83
 
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
84
+ market_response = response_to_model(r, PolymarketGammaResponse)
72
85
 
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:
86
+ markets_to_add = []
87
+ for m in market_response.data:
88
+ # Some Polymarket markets are missing the markets field
89
+ if m.markets is None or m.markets[0].clobTokenIds is None:
76
90
  continue
77
-
78
- if excluded_questions and market.question in excluded_questions:
91
+ if excluded_questions and m.title in excluded_questions:
79
92
  continue
80
93
 
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
94
+ sorted_outcome_list = sorted(m.markets[0].outcomes_list)
95
+ if only_binary:
96
+ # We keep markets that are only Yes,No
97
+ if len(m.markets) > 1 or sorted_outcome_list != [
98
+ POLYMARKET_FALSE_OUTCOME,
99
+ POLYMARKET_TRUE_OUTCOME,
100
+ ]:
101
+ continue
87
102
 
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():
103
+ if not m.startDate or (created_after and created_after > m.startDate):
91
104
  continue
92
105
 
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
- )
106
+ markets_to_add.append(m)
97
107
 
98
- all_markets.append(market_with_prices)
108
+ if only_binary:
109
+ markets_to_add = [
110
+ market
111
+ for market in markets_to_add
112
+ if market.markets is not None and len(market.markets) == 1
113
+ ]
99
114
 
100
- if len(all_markets) >= limit:
101
- break
115
+ # Add the markets from this batch to our results
116
+ all_markets.extend(markets_to_add)
102
117
 
103
- next_cursor = response.next_cursor
118
+ # Update counters
119
+ offset += len(market_response.data)
120
+ remaining -= len(markets_to_add)
104
121
 
105
- if next_cursor == "LTE=":
106
- # 'LTE=' means the end.
122
+ # Stop if we've reached our limit or there are no more results
123
+ if (
124
+ remaining <= 0
125
+ or not market_response.pagination.hasMore
126
+ or len(market_response.data) == 0
127
+ ):
107
128
  break
108
129
 
130
+ # Return exactly the number of items requested (in case we got more due to batch size)
109
131
  return all_markets[:limit]
110
132
 
111
133
 
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
-
134
+ @tenacity.retry(
135
+ stop=tenacity.stop_after_attempt(2),
136
+ wait=tenacity.wait_fixed(1),
137
+ after=lambda x: logger.debug(
138
+ f"get_user_positions failed, attempt={x.attempt_number}."
139
+ ),
140
+ )
141
+ def get_user_positions(
142
+ user_id: ChecksumAddress,
143
+ condition_ids: list[HexBytes] | None = None,
144
+ ) -> list[PolymarketPositionResponse]:
145
+ """Fetch a user's Polymarket positions; optionally filter by condition IDs."""
146
+ url = "https://data-api.polymarket.com/positions"
147
+ # ... rest of implementation ...
148
+ client: httpx.Client = HttpxCachedClient(ttl=timedelta(seconds=60)).get_client()
149
+
150
+ params = {
151
+ "user": user_id,
152
+ "market": ",".join([i.to_0x_hex() for i in condition_ids])
153
+ if condition_ids
154
+ else None,
155
+ "sortBy": "CASHPNL", # Available options: TOKENS, CURRENT, INITIAL, CASHPNL, PERCENTPNL, TITLE, RESOLVING, PRICE
156
+ }
157
+ params = {k: v for k, v in params.items() if v is not None}
124
158
 
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
159
+ response = client.get(url, params=params)
160
+ response.raise_for_status()
161
+ data = response.json()
162
+ items = [PolymarketPositionResponse.model_validate(d) for d in data]
163
+ return items
@@ -0,0 +1,156 @@
1
+ from enum import Enum
2
+ from typing import Dict
3
+
4
+ from py_clob_client.client import ClobClient
5
+ from py_clob_client.clob_types import MarketOrderArgs, OrderType
6
+ from py_clob_client.order_builder.constants import BUY, SELL
7
+ from pydantic import BaseModel
8
+ from web3 import Web3
9
+
10
+ from prediction_market_agent_tooling.chains import POLYGON_CHAIN_ID
11
+ from prediction_market_agent_tooling.config import APIKeys, RPCConfig
12
+ from prediction_market_agent_tooling.gtypes import USD, HexBytes, OutcomeToken, Wei
13
+ from prediction_market_agent_tooling.loggers import logger
14
+ from prediction_market_agent_tooling.markets.polymarket.constants import (
15
+ CTF_EXCHANGE_POLYMARKET,
16
+ NEG_RISK_ADAPTER,
17
+ NEG_RISK_EXCHANGE,
18
+ POLYMARKET_TINY_BET_AMOUNT,
19
+ )
20
+ from prediction_market_agent_tooling.markets.polymarket.polymarket_contracts import (
21
+ PolymarketConditionalTokenContract,
22
+ USDCeContract,
23
+ )
24
+ from prediction_market_agent_tooling.tools.cow.cow_order import handle_allowance
25
+
26
+ HOST = "https://clob.polymarket.com"
27
+
28
+
29
+ class AllowanceResult(BaseModel):
30
+ balance: float
31
+ allowances: Dict[str, float]
32
+
33
+
34
+ class PolymarketPriceSideEnum(str, Enum):
35
+ BUY = "BUY"
36
+ SELL = "SELL"
37
+
38
+
39
+ class OrderStatusEnum(str, Enum):
40
+ MATCHED = "matched"
41
+ LIVE = "live"
42
+ DELAYED = "delayed"
43
+ UNMATCHED = "unmatched"
44
+
45
+
46
+ class CreateOrderResult(BaseModel):
47
+ errorMsg: str
48
+ orderID: str
49
+ transactionsHashes: list[HexBytes]
50
+ status: OrderStatusEnum
51
+ success: bool
52
+
53
+
54
+ class PriceResponse(BaseModel):
55
+ price: float
56
+
57
+
58
+ class ClobManager:
59
+ def __init__(self, api_keys: APIKeys):
60
+ self.api_keys = api_keys
61
+ self.clob_client = ClobClient(
62
+ HOST,
63
+ key=api_keys.bet_from_private_key.get_secret_value(),
64
+ chain_id=POLYGON_CHAIN_ID,
65
+ )
66
+ self.clob_client.set_api_creds(self.clob_client.create_or_derive_api_creds())
67
+ self.polygon_web3 = RPCConfig().get_polygon_web3()
68
+ self.__init_approvals(polygon_web3=self.polygon_web3)
69
+
70
+ def get_token_price(self, token_id: int, side: PolymarketPriceSideEnum) -> USD:
71
+ price_data = self.clob_client.get_price(token_id=token_id, side=side.value)
72
+ price_item = PriceResponse.model_validate(price_data)
73
+ return USD(price_item.price)
74
+
75
+ def _place_market_order(
76
+ self, token_id: int, amount: float, side: PolymarketPriceSideEnum
77
+ ) -> CreateOrderResult:
78
+ """Internal method to place a market order.
79
+
80
+ Args:
81
+ token_id: The token ID to trade
82
+ amount: The amount to trade (USDC for BUY, token shares for SELL)
83
+ side: Either BUY or SELL
84
+
85
+ Returns:
86
+ CreateOrderResult: The result of the order placement
87
+
88
+ Raises:
89
+ ValueError: If usdc_amount is < 1.0 for BUY orders
90
+ """
91
+ if side == PolymarketPriceSideEnum.BUY and amount < 1.0:
92
+ raise ValueError(
93
+ f"usdc_amounts < 1.0 are not supported by Polymarket, got {amount}"
94
+ )
95
+
96
+ # We check allowances first
97
+ self.__init_approvals()
98
+
99
+ order_args = MarketOrderArgs(
100
+ token_id=str(token_id),
101
+ amount=amount,
102
+ side=side.value,
103
+ )
104
+
105
+ logger.info(f"Placing market order: {order_args}")
106
+ signed_order = self.clob_client.create_market_order(order_args)
107
+ resp = self.clob_client.post_order(signed_order, orderType=OrderType.FOK)
108
+ return CreateOrderResult.model_validate(resp)
109
+
110
+ def place_buy_market_order(
111
+ self, token_id: int, usdc_amount: USD
112
+ ) -> CreateOrderResult:
113
+ """Place a market buy order for the given token with the specified USDC amount."""
114
+ return self._place_market_order(token_id, usdc_amount.value, BUY)
115
+
116
+ def place_sell_market_order(
117
+ self, token_id: int, token_shares: OutcomeToken
118
+ ) -> CreateOrderResult:
119
+ """Place a market sell order for the given token with the specified number of shares."""
120
+ return self._place_market_order(token_id, token_shares.value, SELL)
121
+
122
+ def __init_approvals(
123
+ self,
124
+ polygon_web3: Web3 | None = None,
125
+ ) -> None:
126
+ # from https://github.com/Polymarket/agents/blob/main/agents/polymarket/polymarket.py#L341
127
+ polygon_web3 = polygon_web3 or self.polygon_web3
128
+
129
+ usdc = USDCeContract()
130
+
131
+ # When setting allowances on Polymarket, it's important to set a large amount, because
132
+ # every trade reduces the allowance by the amount of the trade.
133
+ large_amount_wei = Wei(int(100 * 1e6)) # 100 USDC in Wei
134
+ amount_to_check_wei = Wei(int(POLYMARKET_TINY_BET_AMOUNT.value * 1e6))
135
+ ctf = PolymarketConditionalTokenContract()
136
+
137
+ for target_address in [
138
+ CTF_EXCHANGE_POLYMARKET,
139
+ NEG_RISK_EXCHANGE,
140
+ NEG_RISK_ADAPTER,
141
+ ]:
142
+ logger.info(f"Checking allowances for {target_address}")
143
+ handle_allowance(
144
+ api_keys=self.api_keys,
145
+ sell_token=usdc.address,
146
+ for_address=target_address,
147
+ amount_to_check_wei=amount_to_check_wei,
148
+ amount_to_set_wei=large_amount_wei,
149
+ web3=polygon_web3,
150
+ )
151
+
152
+ ctf.approve_if_not_approved(
153
+ api_keys=self.api_keys,
154
+ for_address=target_address,
155
+ web3=polygon_web3,
156
+ )
@@ -0,0 +1,15 @@
1
+ from web3 import Web3
2
+
3
+ from prediction_market_agent_tooling.gtypes import USD
4
+
5
+ CTF_EXCHANGE_POLYMARKET = Web3.to_checksum_address(
6
+ "0x4bfb41d5b3570defd03c39a9a4d8de6bd8b8982e"
7
+ )
8
+ NEG_RISK_EXCHANGE = Web3.to_checksum_address(
9
+ "0xC5d563A36AE78145C45a50134d48A1215220f80a"
10
+ )
11
+ NEG_RISK_ADAPTER = Web3.to_checksum_address(
12
+ "0xd91E80cF2E7be2e162c6513ceD06f1dD0dA35296"
13
+ )
14
+ # We reference this value in multiple files
15
+ POLYMARKET_TINY_BET_AMOUNT = USD(1.0)
@@ -1,15 +1,17 @@
1
+ import json
2
+
1
3
  from pydantic import BaseModel
2
4
 
3
5
  from prediction_market_agent_tooling.gtypes import USDC, OutcomeStr, Probability
4
6
  from prediction_market_agent_tooling.markets.data_models import Resolution
5
- from prediction_market_agent_tooling.markets.polymarket.data_models_web import (
6
- POLYMARKET_FALSE_OUTCOME,
7
- POLYMARKET_TRUE_OUTCOME,
8
- PolymarketFullMarket,
9
- construct_polymarket_url,
10
- )
7
+ from prediction_market_agent_tooling.tools.hexbytes_custom import HexBytes
11
8
  from prediction_market_agent_tooling.tools.utils import DatetimeUTC
12
9
 
10
+ POLYMARKET_TRUE_OUTCOME = "Yes"
11
+ POLYMARKET_FALSE_OUTCOME = "No"
12
+
13
+ POLYMARKET_BASE_URL = "https://polymarket.com"
14
+
13
15
 
14
16
  class PolymarketRewards(BaseModel):
15
17
  min_size: int
@@ -26,6 +28,72 @@ 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 token_ids(self) -> list[int]:
44
+ # If market has no token_ids, we halt for safety since it will fail later on.
45
+ if not self.clobTokenIds:
46
+ raise ValueError("Market has no token_ids")
47
+ return [int(i) for i in json.loads(self.clobTokenIds)]
48
+
49
+ @property
50
+ def outcomes_list(self) -> list[OutcomeStr]:
51
+ return [OutcomeStr(i) for i in json.loads(self.outcomes)]
52
+
53
+ @property
54
+ def outcome_prices(self) -> list[float] | None:
55
+ if not self.outcomePrices:
56
+ return None
57
+ return [float(i) for i in json.loads(self.outcomePrices)]
58
+
59
+
60
+ class PolymarketGammaTag(BaseModel):
61
+ label: str
62
+ slug: str
63
+
64
+
65
+ class PolymarketGammaResponseDataItem(BaseModel):
66
+ id: str
67
+ slug: str
68
+ volume: float | None = None
69
+ startDate: DatetimeUTC | None = None
70
+ endDate: DatetimeUTC | None = None
71
+ liquidity: float | None = None
72
+ liquidityClob: float | None = None
73
+ title: str
74
+ description: str | None = None
75
+ archived: bool
76
+ closed: bool
77
+ active: bool
78
+ markets: list[
79
+ PolymarketGammaMarket
80
+ ] | None = None # Some Polymarket markets have missing markets field. We skip these markets manually when retrieving.
81
+ tags: list[PolymarketGammaTag]
82
+
83
+ @property
84
+ def url(self) -> str:
85
+ return construct_polymarket_url(self.slug)
86
+
87
+
88
+ class PolymarketGammaPagination(BaseModel):
89
+ hasMore: bool
90
+
91
+
92
+ class PolymarketGammaResponse(BaseModel):
93
+ data: list[PolymarketGammaResponseDataItem]
94
+ pagination: PolymarketGammaPagination
95
+
96
+
29
97
  class PolymarketMarket(BaseModel):
30
98
  enable_order_book: bool
31
99
  active: bool
@@ -89,19 +157,6 @@ class PolymarketMarket(BaseModel):
89
157
  f"Should not happen, invalid winner tokens: {winner_tokens}"
90
158
  )
91
159
 
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
160
 
106
161
  class MarketsEndpointResponse(BaseModel):
107
162
  limit: int
@@ -138,3 +193,24 @@ class PolymarketMarketWithPrices(PolymarketMarket):
138
193
  raise ValueError(
139
194
  "Should not happen, as we filter only for binary markets in get_polymarket_binary_markets."
140
195
  )
196
+
197
+
198
+ class PolymarketPositionResponse(BaseModel):
199
+ slug: str
200
+ eventSlug: str
201
+ proxyWallet: str
202
+ asset: str
203
+ conditionId: str
204
+ size: float
205
+ currentValue: float
206
+ cashPnl: float
207
+ redeemable: bool
208
+ outcome: str
209
+ outcomeIndex: int
210
+
211
+
212
+ def construct_polymarket_url(slug: str) -> str:
213
+ """
214
+ Note: This works only if it's a single main market, not sub-market of some more general question.
215
+ """
216
+ return f"{POLYMARKET_BASE_URL}/event/{slug}"