prediction-market-agent-tooling 0.67.2__py3-none-any.whl → 0.67.4__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 (30) hide show
  1. prediction_market_agent_tooling/abis/erc1155.abi.json +352 -0
  2. prediction_market_agent_tooling/deploy/agent.py +57 -51
  3. prediction_market_agent_tooling/deploy/betting_strategy.py +8 -5
  4. prediction_market_agent_tooling/markets/agent_market.py +24 -1
  5. prediction_market_agent_tooling/markets/blockchain_utils.py +5 -3
  6. prediction_market_agent_tooling/markets/data_models.py +23 -3
  7. prediction_market_agent_tooling/markets/manifold/manifold.py +2 -1
  8. prediction_market_agent_tooling/markets/metaculus/metaculus.py +2 -1
  9. prediction_market_agent_tooling/markets/omen/omen.py +2 -1
  10. prediction_market_agent_tooling/markets/polymarket/api.py +9 -3
  11. prediction_market_agent_tooling/markets/polymarket/data_models.py +5 -3
  12. prediction_market_agent_tooling/markets/polymarket/polymarket.py +17 -8
  13. prediction_market_agent_tooling/markets/seer/data_models.py +25 -1
  14. prediction_market_agent_tooling/markets/seer/seer.py +85 -26
  15. prediction_market_agent_tooling/markets/seer/seer_contracts.py +18 -0
  16. prediction_market_agent_tooling/markets/seer/seer_subgraph_handler.py +122 -18
  17. prediction_market_agent_tooling/markets/seer/swap_pool_handler.py +4 -1
  18. prediction_market_agent_tooling/tools/contract.py +59 -0
  19. prediction_market_agent_tooling/tools/cow/cow_order.py +4 -1
  20. prediction_market_agent_tooling/tools/hexbytes_custom.py +9 -0
  21. prediction_market_agent_tooling/tools/httpx_cached_client.py +5 -3
  22. prediction_market_agent_tooling/tools/langfuse_client_utils.py +4 -0
  23. prediction_market_agent_tooling/tools/rephrase.py +1 -1
  24. prediction_market_agent_tooling/tools/singleton.py +11 -6
  25. prediction_market_agent_tooling/tools/tokens/auto_deposit.py +57 -0
  26. {prediction_market_agent_tooling-0.67.2.dist-info → prediction_market_agent_tooling-0.67.4.dist-info}/METADATA +1 -1
  27. {prediction_market_agent_tooling-0.67.2.dist-info → prediction_market_agent_tooling-0.67.4.dist-info}/RECORD +30 -29
  28. {prediction_market_agent_tooling-0.67.2.dist-info → prediction_market_agent_tooling-0.67.4.dist-info}/LICENSE +0 -0
  29. {prediction_market_agent_tooling-0.67.2.dist-info → prediction_market_agent_tooling-0.67.4.dist-info}/WHEEL +0 -0
  30. {prediction_market_agent_tooling-0.67.2.dist-info → prediction_market_agent_tooling-0.67.4.dist-info}/entry_points.txt +0 -0
@@ -1,7 +1,7 @@
1
1
  from enum import Enum
2
- from typing import Annotated, Sequence
2
+ from typing import Annotated, Any, Sequence
3
3
 
4
- from pydantic import BaseModel, BeforeValidator, computed_field
4
+ from pydantic import BaseModel, BeforeValidator, computed_field, model_validator
5
5
 
6
6
  from prediction_market_agent_tooling.deploy.constants import (
7
7
  DOWN_OUTCOME_LOWERCASE_IDENTIFIER,
@@ -157,6 +157,15 @@ class CategoricalProbabilisticAnswer(BaseModel):
157
157
  confidence: float
158
158
  reasoning: str | None = None
159
159
 
160
+ @model_validator(mode="before")
161
+ @classmethod
162
+ def _model_validator(cls, data: Any) -> Any:
163
+ if "p_yes" in data:
164
+ return CategoricalProbabilisticAnswer.from_probabilistic_answer(
165
+ ProbabilisticAnswer.model_validate(data)
166
+ ).model_dump()
167
+ return data
168
+
160
169
  @property
161
170
  def probable_resolution(self) -> Resolution:
162
171
  most_likely_outcome = max(
@@ -290,9 +299,20 @@ class Trade(BaseModel):
290
299
  outcome: OutcomeStr
291
300
  amount: USD
292
301
 
302
+ @model_validator(mode="before")
303
+ @classmethod
304
+ def _model_validator(cls, data: Any) -> Any:
305
+ if isinstance(data["outcome"], bool):
306
+ data["outcome"] = (
307
+ YES_OUTCOME_LOWERCASE_IDENTIFIER
308
+ if data["outcome"]
309
+ else NO_OUTCOME_LOWERCASE_IDENTIFIER
310
+ )
311
+ return data
312
+
293
313
 
294
314
  class PlacedTrade(Trade):
295
- id: str | None = None
315
+ id: str
296
316
 
297
317
  @staticmethod
298
318
  def from_trade(trade: Trade, id: str) -> "PlacedTrade":
@@ -10,6 +10,7 @@ from prediction_market_agent_tooling.gtypes import (
10
10
  )
11
11
  from prediction_market_agent_tooling.markets.agent_market import (
12
12
  AgentMarket,
13
+ ConditionalFilterType,
13
14
  FilterBy,
14
15
  MarketFees,
15
16
  QuestionType,
@@ -112,7 +113,7 @@ class ManifoldAgentMarket(AgentMarket):
112
113
  created_after: t.Optional[DatetimeUTC] = None,
113
114
  excluded_questions: set[str] | None = None,
114
115
  question_type: QuestionType = QuestionType.ALL,
115
- include_conditional_markets: bool = False,
116
+ conditional_filter_type: ConditionalFilterType = ConditionalFilterType.ONLY_NOT_CONDITIONAL,
116
117
  ) -> t.Sequence["ManifoldAgentMarket"]:
117
118
  sort: t.Literal["newest", "close-date"] | None
118
119
  if sort_by == SortBy.CLOSING_SOONEST:
@@ -7,6 +7,7 @@ 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
13
  ProcessedMarket,
@@ -74,7 +75,7 @@ class MetaculusAgentMarket(AgentMarket):
74
75
  created_after: t.Optional[DatetimeUTC] = None,
75
76
  excluded_questions: set[str] | None = None,
76
77
  question_type: QuestionType = QuestionType.ALL,
77
- include_conditional_markets: bool = False,
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,6 +23,7 @@ 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
29
  ProcessedMarket,
@@ -381,7 +382,7 @@ class OmenAgentMarket(AgentMarket):
381
382
  created_after: t.Optional[DatetimeUTC] = None,
382
383
  excluded_questions: set[str] | None = None,
383
384
  question_type: QuestionType = QuestionType.ALL,
384
- include_conditional_markets: bool = False,
385
+ conditional_filter_type: ConditionalFilterType = ConditionalFilterType.ONLY_NOT_CONDITIONAL,
385
386
  ) -> t.Sequence["OmenAgentMarket"]:
386
387
  fetch_categorical_markets = question_type == QuestionType.CATEGORICAL
387
388
 
@@ -1,4 +1,5 @@
1
1
  import typing as t
2
+ from datetime import timedelta
2
3
  from enum import Enum
3
4
  from urllib.parse import urljoin
4
5
 
@@ -49,7 +50,7 @@ def get_polymarkets_with_pagination(
49
50
  Binary markets have len(model.markets) == 1.
50
51
  Categorical markets have len(model.markets) > 1
51
52
  """
52
- client: httpx.Client = HttpxCachedClient(ttl=60).get_client()
53
+ client: httpx.Client = HttpxCachedClient(ttl=timedelta(seconds=60)).get_client()
53
54
  all_markets: list[PolymarketGammaResponseDataItem] = []
54
55
  offset = 0
55
56
  remaining = limit
@@ -82,6 +83,9 @@ def get_polymarkets_with_pagination(
82
83
 
83
84
  markets_to_add = []
84
85
  for m in market_response.data:
86
+ # Some Polymarket markets are missing the markets field
87
+ if m.markets is None:
88
+ continue
85
89
  if excluded_questions and m.title in excluded_questions:
86
90
  continue
87
91
 
@@ -94,14 +98,16 @@ def get_polymarkets_with_pagination(
94
98
  ]:
95
99
  continue
96
100
 
97
- if created_after and created_after > m.startDate:
101
+ if not m.startDate or (created_after and created_after > m.startDate):
98
102
  continue
99
103
 
100
104
  markets_to_add.append(m)
101
105
 
102
106
  if only_binary:
103
107
  markets_to_add = [
104
- market for market in market_response.data if len(market.markets) == 1
108
+ market
109
+ for market in markets_to_add
110
+ if market.markets is not None and len(market.markets) == 1
105
111
  ]
106
112
 
107
113
  # Add the markets from this batch to our results
@@ -59,16 +59,18 @@ class PolymarketGammaResponseDataItem(BaseModel):
59
59
  id: str
60
60
  slug: str
61
61
  volume: float | None = None
62
- startDate: DatetimeUTC
62
+ startDate: DatetimeUTC | None = None
63
63
  endDate: DatetimeUTC | None = None
64
64
  liquidity: float | None = None
65
65
  liquidityClob: float | None = None
66
66
  title: str
67
- description: str
67
+ description: str | None = None
68
68
  archived: bool
69
69
  closed: bool
70
70
  active: bool
71
- markets: list[PolymarketGammaMarket]
71
+ markets: list[
72
+ PolymarketGammaMarket
73
+ ] | None = None # Some Polymarket markets have missing markets field. We skip these markets manually when retrieving.
72
74
  tags: list[PolymarketGammaTag]
73
75
 
74
76
  @property
@@ -7,8 +7,10 @@ from prediction_market_agent_tooling.gtypes import (
7
7
  OutcomeStr,
8
8
  Probability,
9
9
  )
10
+ from prediction_market_agent_tooling.loggers import logger
10
11
  from prediction_market_agent_tooling.markets.agent_market import (
11
12
  AgentMarket,
13
+ ConditionalFilterType,
12
14
  FilterBy,
13
15
  MarketFees,
14
16
  QuestionType,
@@ -30,6 +32,7 @@ from prediction_market_agent_tooling.markets.polymarket.polymarket_subgraph_hand
30
32
  PolymarketSubgraphHandler,
31
33
  )
32
34
  from prediction_market_agent_tooling.tools.datetime_utc import DatetimeUTC
35
+ from prediction_market_agent_tooling.tools.utils import check_not_none
33
36
 
34
37
 
35
38
  class PolymarketAgentMarket(AgentMarket):
@@ -68,9 +71,11 @@ class PolymarketAgentMarket(AgentMarket):
68
71
  ]
69
72
  # For a binary market, there should be exactly one payout numerator greater than 0.
70
73
  if len(payout_numerator_indices_gt_0) != 1:
71
- raise ValueError(
72
- f"Only binary markets are supported. Got payout numerators: {condition_model.payoutNumerators}"
74
+ # These cases involve multi-categorical resolution (to be implemented https://github.com/gnosis/prediction-market-agent-tooling/issues/770)
75
+ logger.warning(
76
+ f"Only binary markets are supported. Got payout numerators: {condition_model.payoutNumerators} for condition_id {condition_id.hex()}"
73
77
  )
78
+ return Resolution(outcome=None, invalid=False)
74
79
 
75
80
  # we return the only payout numerator greater than 0 as resolution
76
81
  resolved_outcome = outcomes[payout_numerator_indices_gt_0[0]]
@@ -82,16 +87,16 @@ class PolymarketAgentMarket(AgentMarket):
82
87
  condition_model_dict: dict[HexBytes, ConditionSubgraphModel],
83
88
  ) -> "PolymarketAgentMarket":
84
89
  # If len(model.markets) > 0, this denotes a categorical market.
85
-
86
- outcomes = model.markets[0].outcomes_list
87
- outcome_prices = model.markets[0].outcome_prices
90
+ markets = check_not_none(model.markets)
91
+ outcomes = markets[0].outcomes_list
92
+ outcome_prices = markets[0].outcome_prices
88
93
  if not outcome_prices:
89
94
  # We give random prices
90
95
  outcome_prices = [0.5, 0.5]
91
96
  probabilities = {o: Probability(op) for o, op in zip(outcomes, outcome_prices)}
92
97
 
93
98
  resolution = PolymarketAgentMarket.build_resolution_from_condition(
94
- condition_id=model.markets[0].conditionId,
99
+ condition_id=markets[0].conditionId,
95
100
  condition_model_dict=condition_model_dict,
96
101
  outcomes=outcomes,
97
102
  )
@@ -124,7 +129,7 @@ class PolymarketAgentMarket(AgentMarket):
124
129
  created_after: t.Optional[DatetimeUTC] = None,
125
130
  excluded_questions: set[str] | None = None,
126
131
  question_type: QuestionType = QuestionType.ALL,
127
- include_conditional_markets: bool = False,
132
+ conditional_filter_type: ConditionalFilterType = ConditionalFilterType.ONLY_NOT_CONDITIONAL,
128
133
  ) -> t.Sequence["PolymarketAgentMarket"]:
129
134
  closed: bool | None
130
135
 
@@ -163,7 +168,11 @@ class PolymarketAgentMarket(AgentMarket):
163
168
  )
164
169
 
165
170
  condition_models = PolymarketSubgraphHandler().get_conditions(
166
- condition_ids=[market.markets[0].conditionId for market in markets]
171
+ condition_ids=[
172
+ market.markets[0].conditionId
173
+ for market in markets
174
+ if market.markets is not None
175
+ ]
167
176
  )
168
177
  condition_models_dict = {c.id: c for c in condition_models}
169
178
 
@@ -48,10 +48,14 @@ class CreateCategoricalMarketsParams(BaseModel):
48
48
  SEER_BASE_URL = "https://app.seer.pm"
49
49
 
50
50
 
51
- def seer_normalize_wei(value: int | None) -> int | None:
51
+ def seer_normalize_wei(value: int | dict[str, t.Any] | None) -> int | None:
52
52
  # See https://github.com/seer-pm/demo/blob/main/web/netlify/edge-functions/utils/common.ts#L22
53
53
  if value is None:
54
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"])
55
59
  is_in_wei = value > 1e10
56
60
  return value if is_in_wei else value * 10**18
57
61
 
@@ -59,6 +63,21 @@ def seer_normalize_wei(value: int | None) -> int | None:
59
63
  SeerNormalizedWei = Annotated[Wei | None, BeforeValidator(seer_normalize_wei)]
60
64
 
61
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
+
62
81
  class SeerMarket(BaseModel):
63
82
  model_config = ConfigDict(populate_by_name=True)
64
83
 
@@ -71,6 +90,7 @@ class SeerMarket(BaseModel):
71
90
  alias="parentOutcome", description="It comes as 0 from non-conditioned markets."
72
91
  )
73
92
  parent_market: t.Optional["SeerMarket"] = Field(alias="parentMarket", default=None)
93
+ template_id: int = Field(alias="templateId")
74
94
  collateral_token: HexAddress = Field(alias="collateralToken")
75
95
  condition_id: HexBytes = Field(alias="conditionId")
76
96
  opening_ts: int = Field(alias="openingTs")
@@ -143,6 +163,10 @@ class SeerMarket(BaseModel):
143
163
  return urljoin(SEER_BASE_URL, f"markets/{chain_id}/{self.id.hex()}")
144
164
 
145
165
 
166
+ class SeerMarketWithQuestions(SeerMarket):
167
+ questions: list[SeerMarketQuestions]
168
+
169
+
146
170
  class RedeemParams(BaseModel):
147
171
  model_config = ConfigDict(populate_by_name=True)
148
172
  market: ChecksumAddress
@@ -2,6 +2,7 @@ import asyncio
2
2
  import typing as t
3
3
  from datetime import timedelta
4
4
 
5
+ import cachetools
5
6
  from cowdao_cowpy.common.api.errors import UnexpectedResponseError
6
7
  from eth_typing import ChecksumAddress
7
8
  from web3 import Web3
@@ -23,8 +24,8 @@ from prediction_market_agent_tooling.gtypes import (
23
24
  from prediction_market_agent_tooling.loggers import logger
24
25
  from prediction_market_agent_tooling.markets.agent_market import (
25
26
  AgentMarket,
27
+ ConditionalFilterType,
26
28
  FilterBy,
27
- MarketFees,
28
29
  ParentMarket,
29
30
  ProcessedMarket,
30
31
  ProcessedTradedMarket,
@@ -32,7 +33,10 @@ from prediction_market_agent_tooling.markets.agent_market import (
32
33
  SortBy,
33
34
  )
34
35
  from prediction_market_agent_tooling.markets.blockchain_utils import store_trades
35
- from prediction_market_agent_tooling.markets.data_models import ExistingPosition
36
+ from prediction_market_agent_tooling.markets.data_models import (
37
+ ExistingPosition,
38
+ Resolution,
39
+ )
36
40
  from prediction_market_agent_tooling.markets.market_fees import MarketFees
37
41
  from prediction_market_agent_tooling.markets.omen.omen import OmenAgentMarket
38
42
  from prediction_market_agent_tooling.markets.omen.omen_constants import (
@@ -44,6 +48,7 @@ from prediction_market_agent_tooling.markets.omen.omen_contracts import (
44
48
  from prediction_market_agent_tooling.markets.seer.data_models import (
45
49
  RedeemParams,
46
50
  SeerMarket,
51
+ SeerMarketWithQuestions,
47
52
  )
48
53
  from prediction_market_agent_tooling.markets.seer.exceptions import (
49
54
  PriceCalculationError,
@@ -54,7 +59,9 @@ from prediction_market_agent_tooling.markets.seer.seer_contracts import (
54
59
  SeerMarketFactory,
55
60
  )
56
61
  from prediction_market_agent_tooling.markets.seer.seer_subgraph_handler import (
62
+ SeerQuestionsCache,
57
63
  SeerSubgraphHandler,
64
+ TemplateId,
58
65
  )
59
66
  from prediction_market_agent_tooling.markets.seer.subgraph_data_models import (
60
67
  NewMarketEvent,
@@ -93,6 +100,11 @@ from prediction_market_agent_tooling.tools.utils import check_not_none, utcnow
93
100
  SEER_TINY_BET_AMOUNT = USD(0.1)
94
101
 
95
102
 
103
+ SHARED_CACHE: cachetools.TTLCache[t.Hashable, t.Any] = cachetools.TTLCache(
104
+ maxsize=256, ttl=10 * 60
105
+ )
106
+
107
+
96
108
  class SeerAgentMarket(AgentMarket):
97
109
  wrapped_tokens: list[ChecksumAddress]
98
110
  creator: HexAddress
@@ -257,7 +269,7 @@ class SeerAgentMarket(AgentMarket):
257
269
  @staticmethod
258
270
  def _filter_markets_contained_in_trades(
259
271
  api_keys: APIKeys,
260
- markets: list[SeerMarket],
272
+ markets: t.Sequence[SeerMarket],
261
273
  ) -> list[SeerMarket]:
262
274
  """
263
275
  We filter the markets using previous trades by the user so that we don't have to process all Seer markets.
@@ -267,7 +279,7 @@ class SeerAgentMarket(AgentMarket):
267
279
  traded_tokens = {t.buyToken for t in trades_by_user}.union(
268
280
  [t.sellToken for t in trades_by_user]
269
281
  )
270
- filtered_markets = []
282
+ filtered_markets: list[SeerMarket] = []
271
283
  for market in markets:
272
284
  if any(
273
285
  [
@@ -338,9 +350,64 @@ class SeerAgentMarket(AgentMarket):
338
350
  return OmenAgentMarket.verify_operational_balance(api_keys=api_keys)
339
351
 
340
352
  @staticmethod
341
- def from_data_model_with_subgraph(
353
+ def build_resolution(
354
+ model: SeerMarketWithQuestions,
355
+ ) -> Resolution | None:
356
+ if model.questions[0].question.finalize_ts == 0:
357
+ # resolution not yet finalized
358
+ return None
359
+
360
+ if model.template_id != TemplateId.CATEGORICAL:
361
+ logger.warning("Resolution can only be built for categorical markets.")
362
+ # Future note - for scalar markets, simply fetch best_answer and convert
363
+ # from hex into int and divide by 1e18 (because Wei).
364
+ return None
365
+
366
+ if len(model.questions) != 1:
367
+ raise ValueError("Seer categorical markets must have 1 question.")
368
+
369
+ question = model.questions[0]
370
+ outcome = model.outcomes[int(question.question.best_answer.hex(), 16)]
371
+ return Resolution(outcome=outcome, invalid=False)
372
+
373
+ @staticmethod
374
+ def convert_seer_market_into_market_with_questions(
375
+ seer_market: SeerMarket, seer_subgraph: SeerSubgraphHandler
376
+ ) -> "SeerMarketWithQuestions":
377
+ q = SeerQuestionsCache(seer_subgraph_handler=seer_subgraph)
378
+ q.fetch_questions([seer_market.id])
379
+ questions = q.market_id_to_questions[seer_market.id]
380
+ return SeerMarketWithQuestions(**seer_market.model_dump(), questions=questions)
381
+
382
+ @staticmethod
383
+ def get_parent(
342
384
  model: SeerMarket,
343
385
  seer_subgraph: SeerSubgraphHandler,
386
+ ) -> t.Optional["ParentMarket"]:
387
+ if not model.parent_market:
388
+ return None
389
+
390
+ # turn into a market with questions
391
+ parent_market_with_questions = (
392
+ SeerAgentMarket.convert_seer_market_into_market_with_questions(
393
+ model.parent_market, seer_subgraph=seer_subgraph
394
+ )
395
+ )
396
+
397
+ market_with_questions = check_not_none(
398
+ SeerAgentMarket.from_data_model_with_subgraph(
399
+ parent_market_with_questions, seer_subgraph, False
400
+ )
401
+ )
402
+
403
+ return ParentMarket(
404
+ market=market_with_questions, parent_outcome=model.parent_outcome
405
+ )
406
+
407
+ @staticmethod
408
+ def from_data_model_with_subgraph(
409
+ model: SeerMarketWithQuestions,
410
+ seer_subgraph: SeerSubgraphHandler,
344
411
  must_have_prices: bool,
345
412
  ) -> t.Optional["SeerAgentMarket"]:
346
413
  price_manager = PriceManager(seer_market=model, seer_subgraph=seer_subgraph)
@@ -356,6 +423,10 @@ class SeerAgentMarket(AgentMarket):
356
423
  # Price calculation failed, so don't return the market
357
424
  return None
358
425
 
426
+ resolution = SeerAgentMarket.build_resolution(model=model)
427
+
428
+ parent = SeerAgentMarket.get_parent(model=model, seer_subgraph=seer_subgraph)
429
+
359
430
  market = SeerAgentMarket(
360
431
  id=model.id.hex(),
361
432
  question=model.title,
@@ -370,27 +441,12 @@ class SeerAgentMarket(AgentMarket):
370
441
  fees=MarketFees.get_zero_fees(),
371
442
  outcome_token_pool=None,
372
443
  outcomes_supply=model.outcomes_supply,
373
- resolution=None,
444
+ resolution=resolution,
374
445
  volume=None,
375
446
  probabilities=probability_map,
376
447
  upper_bound=model.upper_bound,
377
448
  lower_bound=model.lower_bound,
378
- parent=(
379
- ParentMarket(
380
- market=(
381
- check_not_none(
382
- SeerAgentMarket.from_data_model_with_subgraph(
383
- model.parent_market,
384
- seer_subgraph,
385
- False,
386
- )
387
- )
388
- ),
389
- parent_outcome=model.parent_outcome,
390
- )
391
- if model.parent_market
392
- else None
393
- ),
449
+ parent=parent,
394
450
  )
395
451
 
396
452
  return market
@@ -403,7 +459,7 @@ class SeerAgentMarket(AgentMarket):
403
459
  created_after: t.Optional[DatetimeUTC] = None,
404
460
  excluded_questions: set[str] | None = None,
405
461
  question_type: QuestionType = QuestionType.ALL,
406
- include_conditional_markets: bool = False,
462
+ conditional_filter_type: ConditionalFilterType = ConditionalFilterType.ONLY_NOT_CONDITIONAL,
407
463
  ) -> t.Sequence["SeerAgentMarket"]:
408
464
  seer_subgraph = SeerSubgraphHandler()
409
465
 
@@ -412,7 +468,7 @@ class SeerAgentMarket(AgentMarket):
412
468
  sort_by=sort_by,
413
469
  filter_by=filter_by,
414
470
  question_type=question_type,
415
- include_conditional_markets=include_conditional_markets,
471
+ conditional_filter_type=conditional_filter_type,
416
472
  )
417
473
 
418
474
  # We exclude the None values below because `from_data_model_with_subgraph` can return None, which
@@ -491,6 +547,7 @@ class SeerAgentMarket(AgentMarket):
491
547
  liquidity = self.get_liquidity_for_outcome(outcome)
492
548
  return liquidity > self.minimum_market_liquidity_required
493
549
 
550
+ @cachetools.cached(cache=SHARED_CACHE, key=lambda self: f"has_liquidity_{self.id}")
494
551
  def has_liquidity(self) -> bool:
495
552
  # We define a market as having liquidity if it has liquidity for all outcomes except for the invalid (index -1)
496
553
  return all(
@@ -544,7 +601,7 @@ class SeerAgentMarket(AgentMarket):
544
601
  f"Expected exactly 1 trade from {order_metadata=}, but got {len(trades)=}."
545
602
  )
546
603
  cow_tx_hash = trades[0].txHash
547
- logger.info(f"TxHash for {order_metadata.uid.root=} is {cow_tx_hash=}.")
604
+ logger.info(f"TxHash is {cow_tx_hash=} for {order_metadata.uid.root=}.")
548
605
  return cow_tx_hash.hex()
549
606
 
550
607
  except (
@@ -576,7 +633,9 @@ class SeerAgentMarket(AgentMarket):
576
633
  amount_wei=amount_wei,
577
634
  web3=web3,
578
635
  )
579
- return tx_receipt["transactionHash"].hex()
636
+ swap_pool_tx_hash = tx_receipt["transactionHash"].hex()
637
+ logger.info(f"TxHash is {swap_pool_tx_hash=}.")
638
+ return swap_pool_tx_hash
580
639
 
581
640
  def place_bet(
582
641
  self,
@@ -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.