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,22 +1,65 @@
1
1
  import typing as t
2
2
 
3
- from prediction_market_agent_tooling.gtypes import USD, CollateralToken, OutcomeStr
3
+ import cachetools
4
+ from web3 import Web3
5
+
6
+ from prediction_market_agent_tooling.config import APIKeys, RPCConfig
7
+ from prediction_market_agent_tooling.gtypes import (
8
+ USD,
9
+ ChecksumAddress,
10
+ CollateralToken,
11
+ HexBytes,
12
+ OutcomeStr,
13
+ OutcomeToken,
14
+ Probability,
15
+ Wei,
16
+ xDai,
17
+ )
18
+ from prediction_market_agent_tooling.loggers import logger
4
19
  from prediction_market_agent_tooling.markets.agent_market import (
5
20
  AgentMarket,
21
+ ConditionalFilterType,
6
22
  FilterBy,
7
23
  MarketFees,
24
+ ProcessedMarket,
25
+ QuestionType,
8
26
  SortBy,
9
27
  )
28
+ from prediction_market_agent_tooling.markets.data_models import (
29
+ ExistingPosition,
30
+ Resolution,
31
+ )
10
32
  from prediction_market_agent_tooling.markets.polymarket.api import (
11
- get_polymarket_binary_markets,
33
+ PolymarketOrderByEnum,
34
+ get_polymarkets_with_pagination,
35
+ get_user_positions,
12
36
  )
13
- from prediction_market_agent_tooling.markets.polymarket.data_models import (
14
- PolymarketMarketWithPrices,
37
+ from prediction_market_agent_tooling.markets.polymarket.clob_manager import (
38
+ ClobManager,
39
+ PolymarketPriceSideEnum,
40
+ )
41
+ from prediction_market_agent_tooling.markets.polymarket.constants import (
42
+ POLYMARKET_TINY_BET_AMOUNT,
15
43
  )
16
- from prediction_market_agent_tooling.markets.polymarket.data_models_web import (
44
+ from prediction_market_agent_tooling.markets.polymarket.data_models import (
17
45
  POLYMARKET_BASE_URL,
46
+ PolymarketGammaResponseDataItem,
47
+ )
48
+ from prediction_market_agent_tooling.markets.polymarket.polymarket_contracts import (
49
+ PolymarketConditionalTokenContract,
50
+ USDCeContract,
51
+ )
52
+ from prediction_market_agent_tooling.markets.polymarket.polymarket_subgraph_handler import (
53
+ ConditionSubgraphModel,
54
+ PolymarketSubgraphHandler,
55
+ )
56
+ from prediction_market_agent_tooling.tools.datetime_utc import DatetimeUTC
57
+ from prediction_market_agent_tooling.tools.tokens.usd import get_token_in_usd
58
+ from prediction_market_agent_tooling.tools.utils import check_not_none
59
+
60
+ SHARED_CACHE: cachetools.TTLCache[t.Hashable, t.Any] = cachetools.TTLCache(
61
+ maxsize=256, ttl=10 * 60
18
62
  )
19
- from prediction_market_agent_tooling.tools.utils import DatetimeUTC
20
63
 
21
64
 
22
65
  class PolymarketAgentMarket(AgentMarket):
@@ -31,25 +74,111 @@ class PolymarketAgentMarket(AgentMarket):
31
74
  # But then in the new subgraph API, they have `fee: BigInt! (Percentage fee of trades taken by market maker. A 2% fee is represented as 2*10^16)`.
32
75
  # TODO: Check out the fees while integrating the subgraph API or if we implement placing of bets on Polymarket.
33
76
  fees: MarketFees = MarketFees.get_zero_fees()
77
+ condition_id: HexBytes
78
+ liquidity_usd: USD
79
+ token_ids: list[int]
80
+ closed_flag_from_polymarket: bool
81
+ active_flag_from_polymarket: bool
34
82
 
35
83
  @staticmethod
36
- def from_data_model(model: PolymarketMarketWithPrices) -> "PolymarketAgentMarket":
84
+ def collateral_token_address() -> ChecksumAddress:
85
+ return USDCeContract().address
86
+
87
+ @staticmethod
88
+ def build_resolution_from_condition(
89
+ condition_id: HexBytes,
90
+ condition_model_dict: dict[HexBytes, ConditionSubgraphModel],
91
+ outcomes: list[OutcomeStr],
92
+ ) -> Resolution | None:
93
+ condition_model = condition_model_dict.get(condition_id)
94
+ if (
95
+ not condition_model
96
+ or condition_model.resolutionTimestamp is None
97
+ or not condition_model.payoutNumerators
98
+ or not condition_model.payoutDenominator
99
+ ):
100
+ return None
101
+
102
+ # Currently we only support binary markets, hence we throw an error if we get something else.
103
+ payout_numerator_indices_gt_0 = [
104
+ idx
105
+ for idx, value in enumerate(condition_model.payoutNumerators)
106
+ if value > 0
107
+ ]
108
+ # For a binary market, there should be exactly one payout numerator greater than 0.
109
+ if len(payout_numerator_indices_gt_0) != 1:
110
+ # These cases involve multi-categorical resolution (to be implemented https://github.com/gnosis/prediction-market-agent-tooling/issues/770)
111
+ logger.warning(
112
+ f"Only binary markets are supported. Got payout numerators: {condition_model.payoutNumerators} for condition_id {condition_id.to_0x_hex()}"
113
+ )
114
+ return Resolution(outcome=None, invalid=False)
115
+
116
+ # we return the only payout numerator greater than 0 as resolution
117
+ resolved_outcome = outcomes[payout_numerator_indices_gt_0[0]]
118
+ return Resolution.from_answer(resolved_outcome)
119
+
120
+ def get_token_id_for_outcome(self, outcome: OutcomeStr) -> int:
121
+ outcome_idx = self.outcomes.index(outcome)
122
+ return self.token_ids[outcome_idx]
123
+
124
+ @staticmethod
125
+ def from_data_model(
126
+ model: PolymarketGammaResponseDataItem,
127
+ condition_model_dict: dict[HexBytes, ConditionSubgraphModel],
128
+ ) -> t.Optional["PolymarketAgentMarket"]:
129
+ # If len(model.markets) > 0, this denotes a categorical market.
130
+ markets = check_not_none(model.markets)
131
+ outcomes = markets[0].outcomes_list
132
+ outcome_prices = markets[0].outcome_prices
133
+ if not outcome_prices:
134
+ logger.info(f"Market has no outcome prices. Skipping. {model=}")
135
+ return None
136
+
137
+ probabilities = {o: Probability(op) for o, op in zip(outcomes, outcome_prices)}
138
+
139
+ condition_id = markets[0].conditionId
140
+ resolution = PolymarketAgentMarket.build_resolution_from_condition(
141
+ condition_id=condition_id,
142
+ condition_model_dict=condition_model_dict,
143
+ outcomes=outcomes,
144
+ )
145
+
37
146
  return PolymarketAgentMarket(
38
147
  id=model.id,
39
- question=model.question,
148
+ condition_id=condition_id,
149
+ question=model.title,
40
150
  description=model.description,
41
- outcomes=[x.outcome for x in model.tokens],
42
- resolution=model.resolution,
43
- created_time=None,
44
- close_time=model.end_date_iso,
151
+ outcomes=outcomes,
152
+ resolution=resolution,
153
+ created_time=model.startDate,
154
+ close_time=model.endDate,
155
+ closed_flag_from_polymarket=model.closed,
156
+ active_flag_from_polymarket=model.active,
45
157
  url=model.url,
46
- volume=None,
158
+ volume=CollateralToken(model.volume) if model.volume else None,
47
159
  outcome_token_pool=None,
48
- probabilities={}, # ToDo - Implement when fixing Polymarket
160
+ probabilities=probabilities,
161
+ liquidity_usd=USD(model.liquidity)
162
+ if model.liquidity is not None
163
+ else USD(0),
164
+ token_ids=markets[0].token_ids,
49
165
  )
50
166
 
51
167
  def get_tiny_bet_amount(self) -> CollateralToken:
52
- raise NotImplementedError("TODO: Implement to allow betting on Polymarket.")
168
+ return CollateralToken(POLYMARKET_TINY_BET_AMOUNT.value)
169
+
170
+ def get_token_in_usd(self, x: CollateralToken) -> USD:
171
+ return get_token_in_usd(x, self.collateral_token_address())
172
+
173
+ @staticmethod
174
+ def get_trade_balance(api_keys: APIKeys, web3: Web3 | None = None) -> USD:
175
+ usdc_balance_wei = USDCeContract().balanceOf(
176
+ for_address=api_keys.public_key, web3=web3
177
+ )
178
+ return USD(usdc_balance_wei.value * 1e-6)
179
+
180
+ def get_liquidity(self, web3: Web3 | None = None) -> CollateralToken:
181
+ return CollateralToken(self.liquidity_usd.value)
53
182
 
54
183
  def place_bet(self, outcome: OutcomeStr, amount: USD) -> str:
55
184
  raise NotImplementedError("TODO: Implement to allow betting on Polymarket.")
@@ -61,15 +190,11 @@ class PolymarketAgentMarket(AgentMarket):
61
190
  filter_by: FilterBy = FilterBy.OPEN,
62
191
  created_after: t.Optional[DatetimeUTC] = None,
63
192
  excluded_questions: set[str] | None = None,
64
- fetch_categorical_markets: bool = False,
193
+ question_type: QuestionType = QuestionType.ALL,
194
+ conditional_filter_type: ConditionalFilterType = ConditionalFilterType.ONLY_NOT_CONDITIONAL,
65
195
  ) -> t.Sequence["PolymarketAgentMarket"]:
66
- if sort_by != SortBy.NONE:
67
- raise ValueError(f"Unsuported sort_by {sort_by} for Polymarket.")
68
-
69
- if created_after is not None:
70
- raise ValueError(f"Unsuported created_after for Polymarket.")
71
-
72
196
  closed: bool | None
197
+
73
198
  if filter_by == FilterBy.OPEN:
74
199
  closed = False
75
200
  elif filter_by == FilterBy.RESOLVED:
@@ -79,11 +204,230 @@ class PolymarketAgentMarket(AgentMarket):
79
204
  else:
80
205
  raise ValueError(f"Unknown filter_by: {filter_by}")
81
206
 
82
- return [
83
- PolymarketAgentMarket.from_data_model(m)
84
- for m in get_polymarket_binary_markets(
85
- limit=limit,
86
- closed=closed,
87
- excluded_questions=excluded_questions,
207
+ ascending: bool = False # default value
208
+ match sort_by:
209
+ case SortBy.NEWEST:
210
+ order_by = PolymarketOrderByEnum.START_DATE
211
+ ascending = False
212
+ case SortBy.CLOSING_SOONEST:
213
+ ascending = True
214
+ order_by = PolymarketOrderByEnum.END_DATE
215
+ case SortBy.HIGHEST_LIQUIDITY:
216
+ order_by = PolymarketOrderByEnum.LIQUIDITY
217
+ case SortBy.NONE:
218
+ order_by = PolymarketOrderByEnum.VOLUME_24HR
219
+ case _:
220
+ raise ValueError(f"Unknown sort_by: {sort_by}")
221
+
222
+ # closed markets also have property active=True, hence ignoring active.
223
+ markets = get_polymarkets_with_pagination(
224
+ limit=limit,
225
+ closed=closed,
226
+ order_by=order_by,
227
+ ascending=ascending,
228
+ created_after=created_after,
229
+ excluded_questions=excluded_questions,
230
+ only_binary=question_type is not QuestionType.CATEGORICAL,
231
+ )
232
+
233
+ condition_models = PolymarketSubgraphHandler().get_conditions(
234
+ condition_ids=list(
235
+ set(
236
+ [
237
+ market.markets[0].conditionId
238
+ for market in markets
239
+ if market.markets is not None
240
+ ]
241
+ )
88
242
  )
89
- ]
243
+ )
244
+ condition_models_dict = {c.id: c for c in condition_models}
245
+
246
+ result_markets: list[PolymarketAgentMarket] = []
247
+ for m in markets:
248
+ market = PolymarketAgentMarket.from_data_model(m, condition_models_dict)
249
+ if market is not None:
250
+ result_markets.append(market)
251
+ return result_markets
252
+
253
+ def ensure_min_native_balance(
254
+ self,
255
+ min_required_balance: xDai,
256
+ multiplier: float = 3.0,
257
+ web3: Web3 | None = None,
258
+ ) -> None:
259
+ balance_collateral = USDCeContract().balanceOf(
260
+ for_address=APIKeys().public_key, web3=web3
261
+ )
262
+ # USDC has 6 decimals, xDAI has 18. We convert from Wei into atomic units.
263
+ balance_collateral_atomic = CollateralToken(float(balance_collateral) / 1e6)
264
+ if balance_collateral_atomic < min_required_balance.as_token:
265
+ raise EnvironmentError(
266
+ f"USDC balance {balance_collateral_atomic} < {min_required_balance.as_token=}"
267
+ )
268
+
269
+ @staticmethod
270
+ def redeem_winnings(api_keys: APIKeys, web3: Web3 | None = None) -> None:
271
+ web3 = web3 or RPCConfig().get_polygon_web3()
272
+ user_id = api_keys.bet_from_address
273
+ conditional_token_contract = PolymarketConditionalTokenContract()
274
+ positions = PolymarketSubgraphHandler().get_market_positions_from_user(user_id)
275
+ for pos in positions:
276
+ if (
277
+ pos.market.condition.resolutionTimestamp is None
278
+ or pos.market.condition.payoutNumerators is None
279
+ ):
280
+ continue
281
+
282
+ condition_id = pos.market.condition.id
283
+ index_sets = pos.market.condition.index_sets
284
+
285
+ redeem_event = conditional_token_contract.redeemPositions(
286
+ api_keys=api_keys,
287
+ collateral_token_address=USDCeContract().address,
288
+ condition_id=condition_id,
289
+ index_sets=index_sets,
290
+ web3=web3,
291
+ )
292
+
293
+ logger.info(f"Redeemed {redeem_event=} from condition_id {condition_id=}.")
294
+
295
+ @staticmethod
296
+ def verify_operational_balance(api_keys: APIKeys) -> bool:
297
+ """Method for checking if agent has enough funds to pay for gas fees."""
298
+ web3 = RPCConfig().get_polygon_web3()
299
+ pol_balance: Wei = Wei(web3.eth.get_balance(api_keys.public_key))
300
+ return pol_balance > Wei(int(0.001 * 1e18))
301
+
302
+ def store_prediction(
303
+ self,
304
+ processed_market: ProcessedMarket | None,
305
+ keys: APIKeys,
306
+ agent_name: str,
307
+ ) -> None:
308
+ pass
309
+
310
+ def store_trades(
311
+ self,
312
+ traded_market: ProcessedMarket | None,
313
+ keys: APIKeys,
314
+ agent_name: str,
315
+ web3: Web3 | None = None,
316
+ ) -> None:
317
+ logger.info("Storing trades deactivated for Polymarket.")
318
+ # Understand how market_id can be represented.
319
+ # Condition_id could work but length doesn't seem to match.
320
+
321
+ @classmethod
322
+ def get_user_url(cls, keys: APIKeys) -> str:
323
+ return f"https://polymarket.com/{keys.public_key}"
324
+
325
+ def get_position(
326
+ self, user_id: str, web3: Web3 | None = None
327
+ ) -> ExistingPosition | None:
328
+ """
329
+ Fetches position from the user in a given market.
330
+ """
331
+ positions = get_user_positions(
332
+ user_id=Web3.to_checksum_address(user_id), condition_ids=[self.condition_id]
333
+ )
334
+ if not positions:
335
+ return None
336
+
337
+ amounts_ot = {i: OutcomeToken(0) for i in self.outcomes}
338
+ amounts_potential = {i: USD(0) for i in self.outcomes}
339
+ amounts_current = {i: USD(0) for i in self.outcomes}
340
+
341
+ for p in positions:
342
+ if p.conditionId != self.condition_id.to_0x_hex():
343
+ continue
344
+
345
+ amounts_potential[OutcomeStr(p.outcome)] = USD(p.size)
346
+ amounts_ot[OutcomeStr(p.outcome)] = OutcomeToken(p.size)
347
+ amounts_current[OutcomeStr(p.outcome)] = USD(p.currentValue)
348
+
349
+ return ExistingPosition(
350
+ amounts_potential=amounts_potential,
351
+ amounts_ot=amounts_ot,
352
+ market_id=self.id,
353
+ amounts_current=amounts_current,
354
+ )
355
+
356
+ def can_be_traded(self) -> bool:
357
+ return (
358
+ self.active_flag_from_polymarket
359
+ and not self.closed_flag_from_polymarket
360
+ and self.liquidity_usd
361
+ > USD(5) # we conservatively require some positive liquidity to trade on
362
+ )
363
+
364
+ def get_buy_token_amount(
365
+ self, bet_amount: USD | CollateralToken, outcome_str: OutcomeStr
366
+ ) -> OutcomeToken:
367
+ """Returns number of outcome tokens returned for a given bet expressed in collateral units."""
368
+
369
+ if outcome_str not in self.outcomes:
370
+ raise ValueError(
371
+ f"Outcome {outcome_str} not found in market outcomes {self.outcomes}"
372
+ )
373
+
374
+ token_id = self.get_token_id_for_outcome(outcome_str)
375
+
376
+ price = ClobManager(APIKeys()).get_token_price(
377
+ token_id=token_id, side=PolymarketPriceSideEnum.BUY
378
+ )
379
+ if not price:
380
+ raise ValueError(
381
+ f"Could not get price for outcome {outcome_str} with token_id {token_id}"
382
+ )
383
+
384
+ # we work with floats since USD and Collateral are the same on Polymarket
385
+ buy_token_amount = bet_amount.value / price.value
386
+ logger.info(f"Buy token amount: {buy_token_amount=}")
387
+ return OutcomeToken(buy_token_amount)
388
+
389
+ def buy_tokens(self, outcome: OutcomeStr, amount: USD) -> str:
390
+ clob_manager = ClobManager(APIKeys())
391
+ token_id = self.get_token_id_for_outcome(outcome)
392
+
393
+ created_order = clob_manager.place_buy_market_order(
394
+ token_id=token_id, usdc_amount=amount
395
+ )
396
+ if not created_order.success:
397
+ raise ValueError(f"Error creating order: {created_order}")
398
+
399
+ return created_order.transactionsHashes[0].to_0x_hex()
400
+
401
+ def sell_tokens(
402
+ self,
403
+ outcome: OutcomeStr,
404
+ amount: USD | OutcomeToken,
405
+ api_keys: APIKeys | None = None,
406
+ ) -> str:
407
+ """
408
+ Polymarket's API expect shares to be sold. 1 share == 1 outcome token / 1e6.
409
+ The number of outcome tokens matches the `balanceOf` of the conditionalTokens contract.
410
+ In comparison, the number of shares match the position.size from the user position.
411
+ """
412
+ logger.info(f"Selling {amount=} from {outcome=}")
413
+ clob_manager = ClobManager(api_keys=api_keys or APIKeys())
414
+ token_id = self.get_token_id_for_outcome(outcome)
415
+ token_shares: OutcomeToken
416
+ if isinstance(amount, OutcomeToken):
417
+ token_shares = amount
418
+ elif isinstance(amount, USD):
419
+ token_price = clob_manager.get_token_price(
420
+ token_id=token_id, side=PolymarketPriceSideEnum.SELL
421
+ )
422
+ # We expect that our order sizes don't move the price too much.
423
+ token_shares = OutcomeToken(amount.value / token_price.value)
424
+ else:
425
+ raise ValueError(f"Unsupported amount type {type(amount)}")
426
+
427
+ created_order = clob_manager.place_sell_market_order(
428
+ token_id=token_id, token_shares=token_shares
429
+ )
430
+ if not created_order.success:
431
+ raise ValueError(f"Error creating order: {created_order}")
432
+
433
+ return created_order.transactionsHashes[0].to_0x_hex()
@@ -0,0 +1,35 @@
1
+ from web3 import Web3
2
+
3
+ from prediction_market_agent_tooling.config import APIKeys
4
+ from prediction_market_agent_tooling.gtypes import ChecksumAddress
5
+ from prediction_market_agent_tooling.tools.contract import (
6
+ ConditionalTokenContract,
7
+ ContractERC20BaseClass,
8
+ ContractOnPolygonChain,
9
+ )
10
+
11
+
12
+ class USDCeContract(ContractERC20BaseClass, ContractOnPolygonChain):
13
+ # USDC.e is used by Polymarket.
14
+ address: ChecksumAddress = Web3.to_checksum_address(
15
+ "0x2791Bca1f2de4661ED88A30C99A7a9449Aa84174"
16
+ )
17
+
18
+
19
+ class PolymarketConditionalTokenContract(
20
+ ConditionalTokenContract, ContractOnPolygonChain
21
+ ):
22
+ address: ChecksumAddress = Web3.to_checksum_address(
23
+ "0x4D97DCd97eC945f40cF65F87097ACe5EA0476045"
24
+ )
25
+
26
+ def approve_if_not_approved(
27
+ self, api_keys: APIKeys, for_address: ChecksumAddress, web3: Web3 | None = None
28
+ ) -> None:
29
+ is_approved = self.isApprovedForAll(
30
+ owner=api_keys.public_key, for_address=for_address, web3=web3
31
+ )
32
+ if not is_approved:
33
+ self.setApprovalForAll(
34
+ api_keys=api_keys, for_address=for_address, approve=True, web3=web3
35
+ )
@@ -0,0 +1,91 @@
1
+ from pydantic import BaseModel
2
+ from subgrounds import FieldPath
3
+
4
+ from prediction_market_agent_tooling.gtypes import ChecksumAddress, HexBytes
5
+ from prediction_market_agent_tooling.markets.base_subgraph_handler import (
6
+ BaseSubgraphHandler,
7
+ )
8
+
9
+
10
+ class ConditionSubgraphModel(BaseModel):
11
+ id: HexBytes
12
+ payoutDenominator: int | None = None
13
+ payoutNumerators: list[int] | None = None
14
+ outcomeSlotCount: int
15
+ resolutionTimestamp: int | None = None
16
+ questionId: HexBytes
17
+
18
+ @property
19
+ def index_sets(self) -> list[int]:
20
+ return [i + 1 for i in range(self.outcomeSlotCount)]
21
+
22
+
23
+ class MarketPositionMarket(BaseModel):
24
+ condition: ConditionSubgraphModel
25
+
26
+
27
+ class MarketPosition(BaseModel):
28
+ id: HexBytes
29
+ market: MarketPositionMarket
30
+
31
+
32
+ class PolymarketSubgraphHandler(BaseSubgraphHandler):
33
+ POLYMARKET_CONDITIONS_SUBGRAPH = "https://gateway.thegraph.com/api/{graph_api_key}/subgraphs/id/81Dm16JjuFSrqz813HysXoUPvzTwE7fsfPk2RTf66nyC"
34
+
35
+ def __init__(self) -> None:
36
+ super().__init__()
37
+
38
+ # Load the subgraph
39
+ self.conditions_subgraph = self.sg.load_subgraph(
40
+ self.POLYMARKET_CONDITIONS_SUBGRAPH.format(
41
+ graph_api_key=self.keys.graph_api_key.get_secret_value()
42
+ )
43
+ )
44
+
45
+ def _get_fields_for_condition(self, field: FieldPath) -> list[FieldPath]:
46
+ return [
47
+ field.id,
48
+ field.questionId,
49
+ field.payoutNumerators,
50
+ field.payoutDenominator,
51
+ field.outcomeSlotCount,
52
+ field.resolutionTimestamp,
53
+ ]
54
+
55
+ def get_conditions(
56
+ self, condition_ids: list[HexBytes]
57
+ ) -> list[ConditionSubgraphModel]:
58
+ where_stms = {"id_in": [i.to_0x_hex() for i in condition_ids]}
59
+ conditions = self.conditions_subgraph.Query.conditions(
60
+ first=len(condition_ids),
61
+ where=where_stms,
62
+ )
63
+
64
+ condition_fields = self._get_fields_for_condition(conditions)
65
+
66
+ conditions_models = self.do_query(
67
+ fields=condition_fields, pydantic_model=ConditionSubgraphModel
68
+ )
69
+ return conditions_models
70
+
71
+ def get_market_positions_from_user(
72
+ self,
73
+ user: ChecksumAddress,
74
+ first: int = 1000,
75
+ block_number: int | None = None,
76
+ ) -> list[MarketPosition]:
77
+ # Not possible to filter using `market_.condition` on a subgraph level, bad indexers error.
78
+ positions = self.conditions_subgraph.Query.marketPositions(
79
+ first=first,
80
+ where={"user": user.lower()},
81
+ block={"number": block_number} if block_number else None,
82
+ )
83
+
84
+ condition_fields = self._get_fields_for_condition(
85
+ positions.market.condition
86
+ ) + [positions.id]
87
+
88
+ positions_models = self.do_query(
89
+ fields=condition_fields, pydantic_model=MarketPosition
90
+ )
91
+ return positions_models
@@ -1,28 +1,7 @@
1
- from prediction_market_agent_tooling.markets.data_models import Resolution
2
- from prediction_market_agent_tooling.markets.markets import MarketType
3
- from prediction_market_agent_tooling.markets.polymarket.data_models_web import (
4
- PolymarketFullMarket,
5
- )
1
+ from prediction_market_agent_tooling.markets.market_type import MarketType
6
2
  from prediction_market_agent_tooling.tools.google_utils import search_google_gcp
7
3
 
8
4
 
9
- def find_resolution_on_polymarket(question: str) -> Resolution | None:
10
- full_market = find_full_polymarket(question)
11
- # TODO: Only main markets are supported right now, add logic for others if needed.
12
- return (
13
- full_market.main_market.resolution
14
- if full_market and full_market.is_main_market
15
- else None
16
- )
17
-
18
-
19
- def find_full_polymarket(question: str) -> PolymarketFullMarket | None:
20
- polymarket_url = find_url_to_polymarket(question)
21
- return (
22
- PolymarketFullMarket.fetch_from_url(polymarket_url) if polymarket_url else None
23
- )
24
-
25
-
26
5
  def find_url_to_polymarket(question: str) -> str | None:
27
6
  # Manually create potential Polymarket's slug from the question.
28
7
  replace_chars = {