prediction-market-agent-tooling 0.61.1.dev477__py3-none-any.whl → 0.61.1.dev483__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.
@@ -5,7 +5,6 @@ from urllib.parse import urljoin
5
5
 
6
6
  from pydantic import BaseModel, ConfigDict, Field
7
7
  from web3 import Web3
8
- from web3.constants import ADDRESS_ZERO
9
8
 
10
9
  from prediction_market_agent_tooling.config import RPCConfig
11
10
  from prediction_market_agent_tooling.gtypes import (
@@ -13,37 +12,13 @@ from prediction_market_agent_tooling.gtypes import (
13
12
  HexAddress,
14
13
  HexBytes,
15
14
  Probability,
16
- Wei,
17
- xdai_type,
18
15
  )
19
- from prediction_market_agent_tooling.loggers import logger
20
16
  from prediction_market_agent_tooling.markets.data_models import Resolution
21
- from prediction_market_agent_tooling.tools.cow.cow_manager import CowManager
17
+ from prediction_market_agent_tooling.markets.seer.price_manager import PriceManager
18
+ from prediction_market_agent_tooling.markets.seer.subgraph_data_models import (
19
+ SeerParentMarket,
20
+ )
22
21
  from prediction_market_agent_tooling.tools.datetime_utc import DatetimeUTC
23
- from prediction_market_agent_tooling.tools.web3_utils import xdai_to_wei
24
-
25
-
26
- class CreateCategoricalMarketsParams(BaseModel):
27
- model_config = ConfigDict(populate_by_name=True)
28
-
29
- market_name: str = Field(..., alias="marketName")
30
- outcomes: list[str]
31
- # Only relevant for scalar markets
32
- question_start: str = Field(alias="questionStart", default="")
33
- question_end: str = Field(alias="questionEnd", default="")
34
- outcome_type: str = Field(alias="outcomeType", default="")
35
-
36
- # Not needed for non-conditional markets.
37
- parent_outcome: int = Field(alias="parentOutcome", default=0)
38
- parent_market: HexAddress = Field(alias="parentMarket", default=ADDRESS_ZERO)
39
-
40
- category: str
41
- lang: str
42
- lower_bound: int = Field(alias="lowerBound", default=0)
43
- upper_bound: int = Field(alias="upperBound", default=0)
44
- min_bond: Wei = Field(..., alias="minBond")
45
- opening_time: int = Field(..., alias="openingTime")
46
- token_names: list[str] = Field(..., alias="tokenNames")
47
22
 
48
23
 
49
24
  class SeerOutcomeEnum(str, Enum):
@@ -84,10 +59,6 @@ class SeerOutcomeEnum(str, Enum):
84
59
  raise ValueError(f"Unknown outcome: {self}")
85
60
 
86
61
 
87
- class SeerParentMarket(BaseModel):
88
- id: HexBytes
89
-
90
-
91
62
  SEER_BASE_URL = "https://app.seer.pm"
92
63
 
93
64
 
@@ -110,6 +81,7 @@ class SeerMarket(BaseModel):
110
81
  has_answers: bool | None = Field(alias="hasAnswers")
111
82
  payout_reported: bool = Field(alias="payoutReported")
112
83
  payout_numerators: list[int] = Field(alias="payoutNumerators")
84
+ outcomes_supply: int = Field(alias="outcomesSupply")
113
85
 
114
86
  @property
115
87
  def has_valid_answer(self) -> bool:
@@ -188,65 +160,10 @@ class SeerMarket(BaseModel):
188
160
 
189
161
  @property
190
162
  def current_p_yes(self) -> Probability:
191
- price_data = {}
192
- for idx in range(len(self.outcomes)):
193
- wrapped_token = self.wrapped_tokens[idx]
194
- price = self._get_price_for_token(
195
- token=Web3.to_checksum_address(wrapped_token)
196
- )
197
- price_data[idx] = price
198
-
199
- if sum(price_data.values()) == 0:
200
- logger.warning(
201
- f"Could not get p_yes for market {self.id.hex()}, all price quotes are 0."
202
- )
203
- return Probability(0)
204
-
205
- yes_idx = self.outcome_as_enums[SeerOutcomeEnum.YES]
206
- price_yes = price_data[yes_idx] / sum(price_data.values())
207
- return Probability(price_yes)
208
-
209
- def _get_price_for_token(self, token: ChecksumAddress) -> float:
210
- collateral_exchange_amount = xdai_to_wei(xdai_type(1))
211
- try:
212
- quote = CowManager().get_quote(
213
- collateral_token=self.collateral_token_contract_address_checksummed,
214
- buy_token=token,
215
- sell_amount=collateral_exchange_amount,
216
- )
217
- except Exception as e:
218
- logger.warning(f"Could not get quote for {token=}, returning price 0. {e=}")
219
- return 0
220
-
221
- return collateral_exchange_amount / float(quote.quote.buyAmount.root)
163
+ p = PriceManager(self)
164
+ return p.current_market_p_yes
222
165
 
223
166
  @property
224
167
  def url(self) -> str:
225
168
  chain_id = RPCConfig().chain_id
226
169
  return urljoin(SEER_BASE_URL, f"markets/{chain_id}/{self.id.hex()}")
227
-
228
-
229
- class SeerToken(BaseModel):
230
- id: HexBytes
231
- name: str
232
- symbol: str
233
-
234
-
235
- class SeerPool(BaseModel):
236
- model_config = ConfigDict(populate_by_name=True)
237
- id: HexBytes
238
- liquidity: int
239
- token0: SeerToken
240
- token1: SeerToken
241
- token0Price: float
242
- token1Price: float
243
- sqrtPrice: int
244
-
245
-
246
- class NewMarketEvent(BaseModel):
247
- market: HexAddress
248
- marketName: str
249
- parentMarket: HexAddress
250
- conditionId: HexBytes
251
- questionId: HexBytes
252
- questionsIds: list[HexBytes]
@@ -0,0 +1,124 @@
1
+ from web3 import Web3
2
+
3
+ from prediction_market_agent_tooling.gtypes import ChecksumAddress
4
+ from prediction_market_agent_tooling.gtypes import Probability, xdai_type
5
+ from prediction_market_agent_tooling.loggers import logger
6
+ from prediction_market_agent_tooling.markets.seer.data_models import (
7
+ SeerMarket,
8
+ SeerOutcomeEnum,
9
+ )
10
+ from prediction_market_agent_tooling.markets.seer.seer_subgraph_handler import (
11
+ SeerSubgraphHandler,
12
+ )
13
+ from prediction_market_agent_tooling.markets.seer.subgraph_data_models import SeerPool
14
+ from prediction_market_agent_tooling.tools.caches.inmemory_cache import (
15
+ persistent_inmemory_cache,
16
+ )
17
+ from prediction_market_agent_tooling.tools.cow.cow_manager import CowManager
18
+ from prediction_market_agent_tooling.tools.web3_utils import xdai_to_wei
19
+
20
+
21
+ class PriceManager:
22
+ def __init__(self, market: SeerMarket):
23
+ self.seer_market = market
24
+ self.subgraph_handler = SeerSubgraphHandler()
25
+
26
+ def fetch_outcome_price_for_seer_market(self) -> float:
27
+ price_data = {}
28
+ for idx in range(len(self.seer_market.outcomes)):
29
+ wrapped_token = self.seer_market.wrapped_tokens[idx]
30
+ price = self.get_price_for_token(
31
+ token=Web3.to_checksum_address(wrapped_token)
32
+ )
33
+ price_data[idx] = price
34
+
35
+ if sum(price_data.values()) == 0:
36
+ logger.warning(
37
+ f"Could not get p_yes for market {self.seer_market.id.hex()}, all price quotes are 0."
38
+ )
39
+ return Probability(0)
40
+
41
+ yes_idx = self.seer_market.outcome_as_enums[SeerOutcomeEnum.YES]
42
+ price_yes = price_data[yes_idx] / sum(price_data.values())
43
+ return Probability(price_yes)
44
+
45
+ def current_market_p_yes(self) -> Probability:
46
+ price_data = {}
47
+ for idx in range(len(self.seer_market.outcomes)):
48
+ wrapped_token = self.seer_market.wrapped_tokens[idx]
49
+ price = self.get_price_for_token(
50
+ token=Web3.to_checksum_address(wrapped_token)
51
+ )
52
+ price_data[idx] = price
53
+
54
+ if sum(price_data.values()) == 0:
55
+ logger.warning(
56
+ f"Could not get p_yes for market {self.seer_market.id.hex()}, all price quotes are 0."
57
+ )
58
+ return Probability(0)
59
+
60
+ yes_idx = self.seer_market.outcome_as_enums[SeerOutcomeEnum.YES]
61
+ no_idx = self.seer_market.outcome_as_enums[SeerOutcomeEnum.NO]
62
+ price_yes = price_data[yes_idx]
63
+ price_no = price_data[no_idx]
64
+ if price_yes and not price_no:
65
+ # We simply return p_yes since it's probably a bug that p_no wasn't found.
66
+ return Probability(price_yes)
67
+ elif price_no and not price_yes:
68
+ # We return the complement of p_no (and ignore invalid).
69
+ return Probability(1.0 - price_no)
70
+ else:
71
+ # If all prices are available, we normalize price_yes by the other prices for the final probability.
72
+ price_yes = price_yes / sum(price_data.values())
73
+ return Probability(price_yes)
74
+
75
+ @persistent_inmemory_cache
76
+ def get_price_for_token(self, token: ChecksumAddress) -> float:
77
+ collateral_exchange_amount = xdai_to_wei(xdai_type(1))
78
+ try:
79
+ quote = CowManager().get_quote(
80
+ collateral_token=self.seer_market.collateral_token_contract_address_checksummed,
81
+ buy_token=token,
82
+ sell_amount=collateral_exchange_amount,
83
+ )
84
+ except Exception as e:
85
+ logger.warning(
86
+ f"Could not get quote for {token=} from Cow, exception {e=}. Falling back to pools. "
87
+ )
88
+ price = self.get_token_price_from_pools(token=token)
89
+ return price
90
+
91
+ return collateral_exchange_amount / float(quote.quote.buyAmount.root)
92
+
93
+ @staticmethod
94
+ def _pool_token0_matches_token(token: ChecksumAddress, pool: SeerPool) -> bool:
95
+ return pool.token0.id.hex().lower() == token.lower()
96
+
97
+ def get_token_price_from_pools(self, token: ChecksumAddress) -> float:
98
+ pool = self.subgraph_handler.get_pool_by_token(token_address=token)
99
+
100
+ if not pool:
101
+ logger.warning(f"Could not find a pool for {token=}, returning 0.")
102
+ return 0
103
+ # Check if other token is market's collateral (sanity check).
104
+
105
+ collateral_address = (
106
+ pool.token0.id
107
+ if self._pool_token0_matches_token(token=token, pool=pool)
108
+ else pool.token1.id
109
+ )
110
+ if (
111
+ collateral_address.hex().lower()
112
+ != self.seer_market.collateral_token.lower()
113
+ ):
114
+ logger.warning(
115
+ f"Pool {pool.id.hex()} has collateral mismatch with market. Collateral from pool {collateral_address.hex()}, collateral from market {self.seer_market.collateral_token}, returning 0."
116
+ )
117
+ return 0
118
+
119
+ price_in_collateral_units = (
120
+ pool.token0Price
121
+ if self._pool_token0_matches_token(pool)
122
+ else pool.token1Price
123
+ )
124
+ return price_in_collateral_units
@@ -352,4 +352,4 @@ def extract_market_address_from_tx(
352
352
  .process_receipt(tx_receipt)
353
353
  )
354
354
  new_market_event = NewMarketEvent(**event_logs[0]["args"])
355
- return Web3.to_checksum_address(new_market_event.market)
355
+ return Web3.to_checksum_address(new_market_event.seer_market)
@@ -5,6 +5,7 @@ from typing import Any
5
5
  from subgrounds import FieldPath
6
6
  from web3.constants import ADDRESS_ZERO
7
7
 
8
+ from prediction_market_agent_tooling.gtypes import ChecksumAddress
8
9
  from prediction_market_agent_tooling.markets.agent_market import FilterBy, SortBy
9
10
  from prediction_market_agent_tooling.markets.base_subgraph_handler import (
10
11
  BaseSubgraphHandler,
@@ -49,6 +50,7 @@ class SeerSubgraphHandler(BaseSubgraphHandler):
49
50
  markets_field.creator,
50
51
  markets_field.conditionId,
51
52
  markets_field.marketName,
53
+ markets_field.outcomesSupply,
52
54
  markets_field.parentOutcome,
53
55
  markets_field.outcomes,
54
56
  markets_field.payoutReported,
@@ -124,8 +126,12 @@ class SeerSubgraphHandler(BaseSubgraphHandler):
124
126
  case SortBy.CLOSING_SOONEST:
125
127
  sort_direction = "asc"
126
128
  sort_by_field = self.seer_subgraph.Market.openingTs
127
- # ToDo - Implement liquidity conditions by looking up Swapr subgraph.
128
- case SortBy.NONE | SortBy.HIGHEST_LIQUIDITY | SortBy.LOWEST_LIQUIDITY:
129
+ case SortBy.HIGHEST_LIQUIDITY | SortBy.LOWEST_LIQUIDITY:
130
+ sort_direction = (
131
+ "desc" if sort_by == SortBy.HIGHEST_LIQUIDITY else "asc"
132
+ )
133
+ sort_by_field = self.seer_subgraph.Market.outcomesSupply
134
+ case SortBy.NONE:
129
135
  sort_direction = None
130
136
  sort_by_field = None
131
137
  case _:
@@ -187,7 +193,7 @@ class SeerSubgraphHandler(BaseSubgraphHandler):
187
193
  return binary_markets
188
194
 
189
195
  def get_market_by_id(self, market_id: HexBytes) -> SeerMarket:
190
- markets_field = self.seer_subgraph.Query.market(id=market_id.hex().lower())
196
+ markets_field = self.seer_subgraph.Query.seer_market(id=market_id.hex().lower())
191
197
  fields = self._get_fields_for_markets(markets_field)
192
198
  markets = self.do_query(fields=fields, pydantic_model=SeerMarket)
193
199
  if len(markets) != 1:
@@ -212,17 +218,19 @@ class SeerSubgraphHandler(BaseSubgraphHandler):
212
218
  ]
213
219
  return fields
214
220
 
215
- def get_swapr_pools_for_market(self, market: SeerMarket) -> list[SeerPool]:
221
+ def get_pool_by_token(self, token_address: ChecksumAddress) -> SeerPool | None:
216
222
  # We iterate through the wrapped tokens and put them in a where clause so that we hit the subgraph endpoint just once.
217
223
  wheres = []
218
- for wrapped_token in market.wrapped_tokens:
219
- wheres.extend(
220
- [
221
- {"token0": wrapped_token.lower()},
222
- {"token1": wrapped_token.lower()},
223
- ]
224
- )
224
+ wheres.extend(
225
+ [
226
+ {"token0": token_address.lower()},
227
+ {"token1": token_address.lower()},
228
+ ]
229
+ )
225
230
  pools_field = self.swapr_algebra_subgraph.Query.pools(where={"or": wheres})
226
231
  fields = self._get_fields_for_pools(pools_field)
227
232
  pools = self.do_query(fields=fields, pydantic_model=SeerPool)
228
- return pools
233
+ # We assume there is only one pool for outcomeToken/sDAI.
234
+ if pools:
235
+ return pools[0]
236
+ return None
@@ -0,0 +1,56 @@
1
+ from pydantic import BaseModel, ConfigDict, Field
2
+
3
+ from prediction_market_agent_tooling.gtypes import HexBytes, HexAddress
4
+
5
+
6
+ class SeerToken(BaseModel):
7
+ id: HexBytes
8
+ name: str
9
+ symbol: str
10
+
11
+
12
+ class SeerPool(BaseModel):
13
+ model_config = ConfigDict(populate_by_name=True)
14
+ id: HexBytes
15
+ liquidity: int
16
+ token0: SeerToken
17
+ token1: SeerToken
18
+ token0Price: float
19
+ token1Price: float
20
+ sqrtPrice: int
21
+
22
+
23
+ class NewMarketEvent(BaseModel):
24
+ market: HexAddress
25
+ marketName: str
26
+ parentMarket: HexAddress
27
+ conditionId: HexBytes
28
+ questionId: HexBytes
29
+ questionsIds: list[HexBytes]
30
+
31
+
32
+ class CreateCategoricalMarketsParams(BaseModel):
33
+ model_config = ConfigDict(populate_by_name=True)
34
+
35
+ market_name: str = Field(..., alias="marketName")
36
+ outcomes: list[str]
37
+ # Only relevant for scalar markets
38
+ question_start: str = Field(alias="questionStart", default="")
39
+ question_end: str = Field(alias="questionEnd", default="")
40
+ outcome_type: str = Field(alias="outcomeType", default="")
41
+
42
+ # Not needed for non-conditional markets.
43
+ parent_outcome: int = Field(alias="parentOutcome", default=0)
44
+ parent_market: HexAddress = Field(alias="parentMarket", default=ADDRESS_ZERO)
45
+
46
+ category: str
47
+ lang: str
48
+ lower_bound: int = Field(alias="lowerBound", default=0)
49
+ upper_bound: int = Field(alias="upperBound", default=0)
50
+ min_bond: Wei = Field(..., alias="minBond")
51
+ opening_time: int = Field(..., alias="openingTime")
52
+ token_names: list[str] = Field(..., alias="tokenNames")
53
+
54
+
55
+ class SeerParentMarket(BaseModel):
56
+ id: HexBytes
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: prediction-market-agent-tooling
3
- Version: 0.61.1.dev477
3
+ Version: 0.61.1.dev483
4
4
  Summary: Tools to benchmark, deploy and monitor prediction market agents.
5
5
  Author: Gnosis
6
6
  Requires-Python: >=3.10,<3.13
@@ -61,10 +61,12 @@ prediction_market_agent_tooling/markets/polymarket/data_models.py,sha256=Fd5PI5y
61
61
  prediction_market_agent_tooling/markets/polymarket/data_models_web.py,sha256=VZhVccTApygSKMmy6Au2G02JCJOKJnR_oVeKlaesuSg,12548
62
62
  prediction_market_agent_tooling/markets/polymarket/polymarket.py,sha256=NRoZK71PtH8kkangMqme7twcAXhRJSSabbmOir-UnAI,3418
63
63
  prediction_market_agent_tooling/markets/polymarket/utils.py,sha256=8kTeVjXPcXC6DkDvWYsZQLY7x8DS6CEp_yznSEazsNU,2037
64
- prediction_market_agent_tooling/markets/seer/data_models.py,sha256=67hUD-Bs_bJd_j0iuxXTHR0lcWNF3Tkk-fnCi6kSSoE,8268
65
- prediction_market_agent_tooling/markets/seer/seer.py,sha256=r21sXj_4_oIG2L1N5l56vEYGI_q2RGem_6G-Sixkbck,12954
64
+ prediction_market_agent_tooling/markets/seer/data_models.py,sha256=EDzVMgBvum400VQl4IUvdnchBszF6M_4LWxMWkeTIHY,5576
65
+ prediction_market_agent_tooling/markets/seer/price_manager.py,sha256=tuozfXp2dwgUQXvD4G4UAOzs-rIm6vgevbi804vv8-0,5102
66
+ prediction_market_agent_tooling/markets/seer/seer.py,sha256=Anxp66UUzIIKt5wgLoqtUjUM-5HQ0wC5f5rDiYwrRRg,12959
66
67
  prediction_market_agent_tooling/markets/seer/seer_contracts.py,sha256=E7CYAKZiK6cg3dyj1kJuIPKSYYUft98F64shF5S0g4s,2730
67
- prediction_market_agent_tooling/markets/seer/seer_subgraph_handler.py,sha256=-XpRmK5gmXCmf1h82PMkNf_AizFdzI90m9P3OzsVfnw,8684
68
+ prediction_market_agent_tooling/markets/seer/seer_subgraph_handler.py,sha256=2M6vmUxMKQU61VNCp4Ykican6XH1LOp257--GjCJZx8,8974
69
+ prediction_market_agent_tooling/markets/seer/subgraph_data_models.py,sha256=uPTCYTg_lS91Pb7JCU-neVEgAQe3zSOtkttlhQGlcBI,1599
68
70
  prediction_market_agent_tooling/monitor/financial_metrics/financial_metrics.py,sha256=fjIgjDIx5MhH5mwf7S0cspLOOSU3elYLhGYoIiM26mU,2746
69
71
  prediction_market_agent_tooling/monitor/markets/manifold.py,sha256=TS4ERwTfQnot8dhekNyVNhJYf5ysYsjF-9v5_kM3aVI,3334
70
72
  prediction_market_agent_tooling/monitor/markets/metaculus.py,sha256=LOnyWWBFdg10-cTWdb76nOsNjDloO8OfMT85GBzRCFI,1455
@@ -117,8 +119,8 @@ prediction_market_agent_tooling/tools/tokens/main_token.py,sha256=7JPgVF4RbiFzLD
117
119
  prediction_market_agent_tooling/tools/transaction_cache.py,sha256=K5YKNL2_tR10Iw2TD9fuP-CTGpBbZtNdgbd0B_R7pjg,1814
118
120
  prediction_market_agent_tooling/tools/utils.py,sha256=jLG4nbEoIzzJiZ4RgMx4Q969Zdl0p0s63p8uET_0Fuw,6440
119
121
  prediction_market_agent_tooling/tools/web3_utils.py,sha256=3wfqNxvMn44ivweFRoeKNVb9QRtFd7kFtp7VUY5juEE,12862
120
- prediction_market_agent_tooling-0.61.1.dev477.dist-info/LICENSE,sha256=6or154nLLU6bELzjh0mCreFjt0m2v72zLi3yHE0QbeE,7650
121
- prediction_market_agent_tooling-0.61.1.dev477.dist-info/METADATA,sha256=cTdPU6VSva5PSgofTUucAuFXtbSnDug4fvTtvi-s1s8,8636
122
- prediction_market_agent_tooling-0.61.1.dev477.dist-info/WHEEL,sha256=XbeZDeTWKc1w7CSIyre5aMDU_-PohRwTQceYnisIYYY,88
123
- prediction_market_agent_tooling-0.61.1.dev477.dist-info/entry_points.txt,sha256=m8PukHbeH5g0IAAmOf_1Ahm-sGAMdhSSRQmwtpmi2s8,81
124
- prediction_market_agent_tooling-0.61.1.dev477.dist-info/RECORD,,
122
+ prediction_market_agent_tooling-0.61.1.dev483.dist-info/LICENSE,sha256=6or154nLLU6bELzjh0mCreFjt0m2v72zLi3yHE0QbeE,7650
123
+ prediction_market_agent_tooling-0.61.1.dev483.dist-info/METADATA,sha256=078bYVh1iho-ehDfULc_ZXtRaQc71KnHdmJErDU_B5k,8636
124
+ prediction_market_agent_tooling-0.61.1.dev483.dist-info/WHEEL,sha256=XbeZDeTWKc1w7CSIyre5aMDU_-PohRwTQceYnisIYYY,88
125
+ prediction_market_agent_tooling-0.61.1.dev483.dist-info/entry_points.txt,sha256=m8PukHbeH5g0IAAmOf_1Ahm-sGAMdhSSRQmwtpmi2s8,81
126
+ prediction_market_agent_tooling-0.61.1.dev483.dist-info/RECORD,,