prediction-market-agent-tooling 0.67.1__py3-none-any.whl → 0.67.3__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 (24) hide show
  1. prediction_market_agent_tooling/abis/erc1155.abi.json +352 -0
  2. prediction_market_agent_tooling/deploy/agent.py +109 -47
  3. prediction_market_agent_tooling/deploy/betting_strategy.py +1 -1
  4. prediction_market_agent_tooling/markets/agent_market.py +16 -3
  5. prediction_market_agent_tooling/markets/manifold/manifold.py +4 -3
  6. prediction_market_agent_tooling/markets/markets.py +6 -5
  7. prediction_market_agent_tooling/markets/metaculus/metaculus.py +4 -3
  8. prediction_market_agent_tooling/markets/omen/omen.py +5 -4
  9. prediction_market_agent_tooling/markets/polymarket/polymarket.py +5 -4
  10. prediction_market_agent_tooling/markets/seer/data_models.py +30 -8
  11. prediction_market_agent_tooling/markets/seer/seer.py +79 -20
  12. prediction_market_agent_tooling/markets/seer/seer_contracts.py +18 -0
  13. prediction_market_agent_tooling/markets/seer/seer_subgraph_handler.py +149 -20
  14. prediction_market_agent_tooling/markets/seer/subgraph_data_models.py +0 -4
  15. prediction_market_agent_tooling/markets/seer/swap_pool_handler.py +0 -10
  16. prediction_market_agent_tooling/tools/contract.py +59 -0
  17. prediction_market_agent_tooling/tools/cow/cow_order.py +4 -1
  18. prediction_market_agent_tooling/tools/rephrase.py +71 -0
  19. prediction_market_agent_tooling/tools/tokens/auto_deposit.py +57 -0
  20. {prediction_market_agent_tooling-0.67.1.dist-info → prediction_market_agent_tooling-0.67.3.dist-info}/METADATA +2 -1
  21. {prediction_market_agent_tooling-0.67.1.dist-info → prediction_market_agent_tooling-0.67.3.dist-info}/RECORD +24 -22
  22. {prediction_market_agent_tooling-0.67.1.dist-info → prediction_market_agent_tooling-0.67.3.dist-info}/LICENSE +0 -0
  23. {prediction_market_agent_tooling-0.67.1.dist-info → prediction_market_agent_tooling-0.67.3.dist-info}/WHEEL +0 -0
  24. {prediction_market_agent_tooling-0.67.1.dist-info → prediction_market_agent_tooling-0.67.3.dist-info}/entry_points.txt +0 -0
@@ -3,11 +3,12 @@ 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
7
6
  from prediction_market_agent_tooling.markets.agent_market import (
8
- MarketType as AgentMarketType,
7
+ AgentMarket,
8
+ FilterBy,
9
+ QuestionType,
10
+ SortBy,
9
11
  )
10
- from prediction_market_agent_tooling.markets.agent_market import SortBy
11
12
  from prediction_market_agent_tooling.markets.manifold.manifold import (
12
13
  ManifoldAgentMarket,
13
14
  )
@@ -68,7 +69,7 @@ def get_binary_markets(
68
69
  sort_by: SortBy = SortBy.NONE,
69
70
  excluded_questions: set[str] | None = None,
70
71
  created_after: DatetimeUTC | None = None,
71
- agent_market_type: AgentMarketType = AgentMarketType.BINARY,
72
+ question_type: QuestionType = QuestionType.BINARY,
72
73
  ) -> t.Sequence[AgentMarket]:
73
74
  agent_market_class = MARKET_TYPE_TO_AGENT_MARKET[market_type]
74
75
  markets = agent_market_class.get_markets(
@@ -77,6 +78,6 @@ def get_binary_markets(
77
78
  filter_by=filter_by,
78
79
  created_after=created_after,
79
80
  excluded_questions=excluded_questions,
80
- market_type=agent_market_type,
81
+ question_type=question_type,
81
82
  )
82
83
  return markets
@@ -7,10 +7,11 @@ from prediction_market_agent_tooling.config import APIKeys
7
7
  from prediction_market_agent_tooling.gtypes import OutcomeStr, Probability
8
8
  from prediction_market_agent_tooling.markets.agent_market import (
9
9
  AgentMarket,
10
+ ConditionalFilterType,
10
11
  FilterBy,
11
12
  MarketFees,
12
- MarketType,
13
13
  ProcessedMarket,
14
+ QuestionType,
14
15
  SortBy,
15
16
  )
16
17
  from prediction_market_agent_tooling.markets.metaculus.api import (
@@ -73,8 +74,8 @@ class MetaculusAgentMarket(AgentMarket):
73
74
  filter_by: FilterBy = FilterBy.OPEN,
74
75
  created_after: t.Optional[DatetimeUTC] = None,
75
76
  excluded_questions: set[str] | None = None,
76
- market_type: MarketType = MarketType.ALL,
77
- include_conditional_markets: bool = False,
77
+ question_type: QuestionType = QuestionType.ALL,
78
+ conditional_filter_type: ConditionalFilterType = ConditionalFilterType.ONLY_NOT_CONDITIONAL,
78
79
  tournament_id: int | None = None,
79
80
  ) -> t.Sequence["MetaculusAgentMarket"]:
80
81
  order_by: str | None
@@ -23,11 +23,12 @@ from prediction_market_agent_tooling.gtypes import (
23
23
  from prediction_market_agent_tooling.loggers import logger
24
24
  from prediction_market_agent_tooling.markets.agent_market import (
25
25
  AgentMarket,
26
+ ConditionalFilterType,
26
27
  FilterBy,
27
28
  MarketFees,
28
- MarketType,
29
29
  ProcessedMarket,
30
30
  ProcessedTradedMarket,
31
+ QuestionType,
31
32
  SortBy,
32
33
  )
33
34
  from prediction_market_agent_tooling.markets.blockchain_utils import store_trades
@@ -380,10 +381,10 @@ class OmenAgentMarket(AgentMarket):
380
381
  filter_by: FilterBy = FilterBy.OPEN,
381
382
  created_after: t.Optional[DatetimeUTC] = None,
382
383
  excluded_questions: set[str] | None = None,
383
- market_type: MarketType = MarketType.ALL,
384
- include_conditional_markets: bool = False,
384
+ question_type: QuestionType = QuestionType.ALL,
385
+ conditional_filter_type: ConditionalFilterType = ConditionalFilterType.ONLY_NOT_CONDITIONAL,
385
386
  ) -> t.Sequence["OmenAgentMarket"]:
386
- fetch_categorical_markets = market_type == MarketType.CATEGORICAL
387
+ fetch_categorical_markets = question_type == QuestionType.CATEGORICAL
387
388
 
388
389
  return [
389
390
  OmenAgentMarket.from_data_model(m)
@@ -9,9 +9,10 @@ from prediction_market_agent_tooling.gtypes import (
9
9
  )
10
10
  from prediction_market_agent_tooling.markets.agent_market import (
11
11
  AgentMarket,
12
+ ConditionalFilterType,
12
13
  FilterBy,
13
14
  MarketFees,
14
- MarketType,
15
+ QuestionType,
15
16
  SortBy,
16
17
  )
17
18
  from prediction_market_agent_tooling.markets.data_models import Resolution
@@ -123,8 +124,8 @@ class PolymarketAgentMarket(AgentMarket):
123
124
  filter_by: FilterBy = FilterBy.OPEN,
124
125
  created_after: t.Optional[DatetimeUTC] = None,
125
126
  excluded_questions: set[str] | None = None,
126
- market_type: MarketType = MarketType.ALL,
127
- include_conditional_markets: bool = False,
127
+ question_type: QuestionType = QuestionType.ALL,
128
+ conditional_filter_type: ConditionalFilterType = ConditionalFilterType.ONLY_NOT_CONDITIONAL,
128
129
  ) -> t.Sequence["PolymarketAgentMarket"]:
129
130
  closed: bool | None
130
131
 
@@ -159,7 +160,7 @@ class PolymarketAgentMarket(AgentMarket):
159
160
  ascending=ascending,
160
161
  created_after=created_after,
161
162
  excluded_questions=excluded_questions,
162
- only_binary=market_type is not MarketType.CATEGORICAL,
163
+ only_binary=question_type is not QuestionType.CATEGORICAL,
163
164
  )
164
165
 
165
166
  condition_models = PolymarketSubgraphHandler().get_conditions(
@@ -3,7 +3,7 @@ from datetime import timedelta
3
3
  from typing import Annotated
4
4
  from urllib.parse import urljoin
5
5
 
6
- from pydantic import BaseModel, BeforeValidator, ConfigDict, Field
6
+ from pydantic import BaseModel, BeforeValidator, ConfigDict, Field, computed_field
7
7
  from web3 import Web3
8
8
  from web3.constants import ADDRESS_ZERO
9
9
 
@@ -17,9 +17,6 @@ from prediction_market_agent_tooling.gtypes import (
17
17
  Web3Wei,
18
18
  Wei,
19
19
  )
20
- from prediction_market_agent_tooling.markets.seer.subgraph_data_models import (
21
- SeerParentMarket,
22
- )
23
20
  from prediction_market_agent_tooling.tools.contract import ContractERC20OnGnosisChain
24
21
  from prediction_market_agent_tooling.tools.datetime_utc import DatetimeUTC
25
22
  from prediction_market_agent_tooling.tools.utils import utcnow
@@ -51,10 +48,14 @@ class CreateCategoricalMarketsParams(BaseModel):
51
48
  SEER_BASE_URL = "https://app.seer.pm"
52
49
 
53
50
 
54
- def seer_normalize_wei(value: int | None) -> int | None:
51
+ def seer_normalize_wei(value: int | dict[str, t.Any] | None) -> int | None:
55
52
  # See https://github.com/seer-pm/demo/blob/main/web/netlify/edge-functions/utils/common.ts#L22
56
53
  if value is None:
57
54
  return value
55
+ elif isinstance(value, dict):
56
+ if value.get("value") is None:
57
+ raise ValueError(f"Expected a dictionary with a value key, but got {value}")
58
+ value = int(value["value"])
58
59
  is_in_wei = value > 1e10
59
60
  return value if is_in_wei else value * 10**18
60
61
 
@@ -62,6 +63,21 @@ def seer_normalize_wei(value: int | None) -> int | None:
62
63
  SeerNormalizedWei = Annotated[Wei | None, BeforeValidator(seer_normalize_wei)]
63
64
 
64
65
 
66
+ class MarketId(BaseModel):
67
+ id: HexBytes
68
+
69
+
70
+ class SeerQuestion(BaseModel):
71
+ id: str
72
+ best_answer: HexBytes
73
+ finalize_ts: int
74
+
75
+
76
+ class SeerMarketQuestions(BaseModel):
77
+ question: SeerQuestion
78
+ market: MarketId
79
+
80
+
65
81
  class SeerMarket(BaseModel):
66
82
  model_config = ConfigDict(populate_by_name=True)
67
83
 
@@ -70,10 +86,11 @@ class SeerMarket(BaseModel):
70
86
  title: str = Field(alias="marketName")
71
87
  outcomes: t.Sequence[OutcomeStr]
72
88
  wrapped_tokens: list[HexAddress] = Field(alias="wrappedTokens")
73
- parent_outcome: int = Field(alias="parentOutcome")
74
- parent_market: t.Optional[SeerParentMarket] = Field(
75
- alias="parentMarket", default=None
89
+ parent_outcome: int = Field(
90
+ alias="parentOutcome", description="It comes as 0 from non-conditioned markets."
76
91
  )
92
+ parent_market: t.Optional["SeerMarket"] = Field(alias="parentMarket", default=None)
93
+ template_id: int = Field(alias="templateId")
77
94
  collateral_token: HexAddress = Field(alias="collateralToken")
78
95
  condition_id: HexBytes = Field(alias="conditionId")
79
96
  opening_ts: int = Field(alias="openingTs")
@@ -139,12 +156,17 @@ class SeerMarket(BaseModel):
139
156
  def created_time(self) -> DatetimeUTC:
140
157
  return DatetimeUTC.to_datetime_utc(self.block_timestamp)
141
158
 
159
+ @computed_field # type: ignore[prop-decorator]
142
160
  @property
143
161
  def url(self) -> str:
144
162
  chain_id = RPCConfig().chain_id
145
163
  return urljoin(SEER_BASE_URL, f"markets/{chain_id}/{self.id.hex()}")
146
164
 
147
165
 
166
+ class SeerMarketWithQuestions(SeerMarket):
167
+ questions: list[SeerMarketQuestions]
168
+
169
+
148
170
  class RedeemParams(BaseModel):
149
171
  model_config = ConfigDict(populate_by_name=True)
150
172
  market: ChecksumAddress
@@ -23,15 +23,19 @@ from prediction_market_agent_tooling.gtypes import (
23
23
  from prediction_market_agent_tooling.loggers import logger
24
24
  from prediction_market_agent_tooling.markets.agent_market import (
25
25
  AgentMarket,
26
+ ConditionalFilterType,
26
27
  FilterBy,
27
- MarketFees,
28
- MarketType,
28
+ ParentMarket,
29
29
  ProcessedMarket,
30
30
  ProcessedTradedMarket,
31
+ QuestionType,
31
32
  SortBy,
32
33
  )
33
34
  from prediction_market_agent_tooling.markets.blockchain_utils import store_trades
34
- from prediction_market_agent_tooling.markets.data_models import ExistingPosition
35
+ from prediction_market_agent_tooling.markets.data_models import (
36
+ ExistingPosition,
37
+ Resolution,
38
+ )
35
39
  from prediction_market_agent_tooling.markets.market_fees import MarketFees
36
40
  from prediction_market_agent_tooling.markets.omen.omen import OmenAgentMarket
37
41
  from prediction_market_agent_tooling.markets.omen.omen_constants import (
@@ -43,6 +47,7 @@ from prediction_market_agent_tooling.markets.omen.omen_contracts import (
43
47
  from prediction_market_agent_tooling.markets.seer.data_models import (
44
48
  RedeemParams,
45
49
  SeerMarket,
50
+ SeerMarketWithQuestions,
46
51
  )
47
52
  from prediction_market_agent_tooling.markets.seer.exceptions import (
48
53
  PriceCalculationError,
@@ -53,7 +58,9 @@ from prediction_market_agent_tooling.markets.seer.seer_contracts import (
53
58
  SeerMarketFactory,
54
59
  )
55
60
  from prediction_market_agent_tooling.markets.seer.seer_subgraph_handler import (
61
+ SeerQuestionsCache,
56
62
  SeerSubgraphHandler,
63
+ TemplateId,
57
64
  )
58
65
  from prediction_market_agent_tooling.markets.seer.subgraph_data_models import (
59
66
  NewMarketEvent,
@@ -86,7 +93,7 @@ from prediction_market_agent_tooling.tools.tokens.usd import (
86
93
  get_token_in_usd,
87
94
  get_usd_in_token,
88
95
  )
89
- from prediction_market_agent_tooling.tools.utils import utcnow
96
+ from prediction_market_agent_tooling.tools.utils import check_not_none, utcnow
90
97
 
91
98
  # We place a larger bet amount by default than Omen so that cow presents valid quotes.
92
99
  SEER_TINY_BET_AMOUNT = USD(0.1)
@@ -256,7 +263,7 @@ class SeerAgentMarket(AgentMarket):
256
263
  @staticmethod
257
264
  def _filter_markets_contained_in_trades(
258
265
  api_keys: APIKeys,
259
- markets: list[SeerMarket],
266
+ markets: t.Sequence[SeerMarket],
260
267
  ) -> list[SeerMarket]:
261
268
  """
262
269
  We filter the markets using previous trades by the user so that we don't have to process all Seer markets.
@@ -266,7 +273,7 @@ class SeerAgentMarket(AgentMarket):
266
273
  traded_tokens = {t.buyToken for t in trades_by_user}.union(
267
274
  [t.sellToken for t in trades_by_user]
268
275
  )
269
- filtered_markets = []
276
+ filtered_markets: list[SeerMarket] = []
270
277
  for market in markets:
271
278
  if any(
272
279
  [
@@ -337,9 +344,64 @@ class SeerAgentMarket(AgentMarket):
337
344
  return OmenAgentMarket.verify_operational_balance(api_keys=api_keys)
338
345
 
339
346
  @staticmethod
340
- def from_data_model_with_subgraph(
347
+ def build_resolution(
348
+ model: SeerMarketWithQuestions,
349
+ ) -> Resolution | None:
350
+ if model.questions[0].question.finalize_ts == 0:
351
+ # resolution not yet finalized
352
+ return None
353
+
354
+ if model.template_id != TemplateId.CATEGORICAL:
355
+ logger.warning("Resolution can only be built for categorical markets.")
356
+ # Future note - for scalar markets, simply fetch best_answer and convert
357
+ # from hex into int and divide by 1e18 (because Wei).
358
+ return None
359
+
360
+ if len(model.questions) != 1:
361
+ raise ValueError("Seer categorical markets must have 1 question.")
362
+
363
+ question = model.questions[0]
364
+ outcome = model.outcomes[int(question.question.best_answer.hex(), 16)]
365
+ return Resolution(outcome=outcome, invalid=False)
366
+
367
+ @staticmethod
368
+ def convert_seer_market_into_market_with_questions(
369
+ seer_market: SeerMarket, seer_subgraph: SeerSubgraphHandler
370
+ ) -> "SeerMarketWithQuestions":
371
+ q = SeerQuestionsCache(seer_subgraph_handler=seer_subgraph)
372
+ q.fetch_questions([seer_market.id])
373
+ questions = q.market_id_to_questions[seer_market.id]
374
+ return SeerMarketWithQuestions(**seer_market.model_dump(), questions=questions)
375
+
376
+ @staticmethod
377
+ def get_parent(
341
378
  model: SeerMarket,
342
379
  seer_subgraph: SeerSubgraphHandler,
380
+ ) -> t.Optional["ParentMarket"]:
381
+ if not model.parent_market:
382
+ return None
383
+
384
+ # turn into a market with questions
385
+ parent_market_with_questions = (
386
+ SeerAgentMarket.convert_seer_market_into_market_with_questions(
387
+ model.parent_market, seer_subgraph=seer_subgraph
388
+ )
389
+ )
390
+
391
+ market_with_questions = check_not_none(
392
+ SeerAgentMarket.from_data_model_with_subgraph(
393
+ parent_market_with_questions, seer_subgraph, False
394
+ )
395
+ )
396
+
397
+ return ParentMarket(
398
+ market=market_with_questions, parent_outcome=model.parent_outcome
399
+ )
400
+
401
+ @staticmethod
402
+ def from_data_model_with_subgraph(
403
+ model: SeerMarketWithQuestions,
404
+ seer_subgraph: SeerSubgraphHandler,
343
405
  must_have_prices: bool,
344
406
  ) -> t.Optional["SeerAgentMarket"]:
345
407
  price_manager = PriceManager(seer_market=model, seer_subgraph=seer_subgraph)
@@ -355,6 +417,10 @@ class SeerAgentMarket(AgentMarket):
355
417
  # Price calculation failed, so don't return the market
356
418
  return None
357
419
 
420
+ resolution = SeerAgentMarket.build_resolution(model=model)
421
+
422
+ parent = SeerAgentMarket.get_parent(model=model, seer_subgraph=seer_subgraph)
423
+
358
424
  market = SeerAgentMarket(
359
425
  id=model.id.hex(),
360
426
  question=model.title,
@@ -369,11 +435,12 @@ class SeerAgentMarket(AgentMarket):
369
435
  fees=MarketFees.get_zero_fees(),
370
436
  outcome_token_pool=None,
371
437
  outcomes_supply=model.outcomes_supply,
372
- resolution=None,
438
+ resolution=resolution,
373
439
  volume=None,
374
440
  probabilities=probability_map,
375
441
  upper_bound=model.upper_bound,
376
442
  lower_bound=model.lower_bound,
443
+ parent=parent,
377
444
  )
378
445
 
379
446
  return market
@@ -385,8 +452,8 @@ class SeerAgentMarket(AgentMarket):
385
452
  filter_by: FilterBy = FilterBy.OPEN,
386
453
  created_after: t.Optional[DatetimeUTC] = None,
387
454
  excluded_questions: set[str] | None = None,
388
- market_type: MarketType = MarketType.ALL,
389
- include_conditional_markets: bool = False,
455
+ question_type: QuestionType = QuestionType.ALL,
456
+ conditional_filter_type: ConditionalFilterType = ConditionalFilterType.ONLY_NOT_CONDITIONAL,
390
457
  ) -> t.Sequence["SeerAgentMarket"]:
391
458
  seer_subgraph = SeerSubgraphHandler()
392
459
 
@@ -394,8 +461,8 @@ class SeerAgentMarket(AgentMarket):
394
461
  limit=limit,
395
462
  sort_by=sort_by,
396
463
  filter_by=filter_by,
397
- market_type=market_type,
398
- include_conditional_markets=False,
464
+ question_type=question_type,
465
+ conditional_filter_type=conditional_filter_type,
399
466
  )
400
467
 
401
468
  # We exclude the None values below because `from_data_model_with_subgraph` can return None, which
@@ -585,14 +652,6 @@ class SeerAgentMarket(AgentMarket):
585
652
  collateral_contract, amount_wei, api_keys, web3
586
653
  )
587
654
 
588
- collateral_balance = collateral_contract.balanceOf(
589
- api_keys.bet_from_address, web3=web3
590
- )
591
- if collateral_balance < amount_wei:
592
- raise ValueError(
593
- f"Balance {collateral_balance} not enough for bet size {amount}"
594
- )
595
-
596
655
  return self._swap_tokens_with_fallback(
597
656
  sell_token=collateral_contract.address,
598
657
  buy_token=outcome_token,
@@ -9,6 +9,7 @@ from prediction_market_agent_tooling.gtypes import (
9
9
  ChecksumAddress,
10
10
  OutcomeStr,
11
11
  TxReceipt,
12
+ Wei,
12
13
  xDai,
13
14
  )
14
15
  from prediction_market_agent_tooling.markets.seer.data_models import (
@@ -115,6 +116,23 @@ class GnosisRouter(ContractOnGnosisChain):
115
116
  )
116
117
  return receipt_tx
117
118
 
119
+ def split_position(
120
+ self,
121
+ api_keys: APIKeys,
122
+ collateral_token: ChecksumAddress,
123
+ market_id: ChecksumAddress,
124
+ amount: Wei,
125
+ web3: Web3 | None = None,
126
+ ) -> TxReceipt:
127
+ """Splits collateral token into full set of outcome tokens."""
128
+ receipt_tx = self.send(
129
+ api_keys=api_keys,
130
+ function_name="splitPosition",
131
+ function_params=[collateral_token, market_id, amount],
132
+ web3=web3,
133
+ )
134
+ return receipt_tx
135
+
118
136
 
119
137
  class SwaprRouterContract(ContractOnGnosisChain):
120
138
  # File content taken from https://github.com/protofire/omen-exchange/blob/master/app/src/abi/marketMaker.json.