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,7 +1,10 @@
1
1
  import typing as t
2
+ from datetime import timedelta
3
+ from enum import Enum
4
+ from typing import Annotated
2
5
  from urllib.parse import urljoin
3
6
 
4
- from pydantic import BaseModel, ConfigDict, Field
7
+ from pydantic import BaseModel, BeforeValidator, ConfigDict, Field, computed_field
5
8
  from web3 import Web3
6
9
  from web3.constants import ADDRESS_ZERO
7
10
 
@@ -13,12 +16,11 @@ from prediction_market_agent_tooling.gtypes import (
13
16
  OutcomeStr,
14
17
  OutcomeWei,
15
18
  Web3Wei,
16
- )
17
- from prediction_market_agent_tooling.markets.seer.subgraph_data_models import (
18
- SeerParentMarket,
19
+ Wei,
19
20
  )
20
21
  from prediction_market_agent_tooling.tools.contract import ContractERC20OnGnosisChain
21
22
  from prediction_market_agent_tooling.tools.datetime_utc import DatetimeUTC
23
+ from prediction_market_agent_tooling.tools.utils import utcnow
22
24
 
23
25
 
24
26
  class CreateCategoricalMarketsParams(BaseModel):
@@ -47,6 +49,36 @@ class CreateCategoricalMarketsParams(BaseModel):
47
49
  SEER_BASE_URL = "https://app.seer.pm"
48
50
 
49
51
 
52
+ def seer_normalize_wei(value: int | dict[str, t.Any] | None) -> int | None:
53
+ # See https://github.com/seer-pm/demo/blob/main/web/netlify/edge-functions/utils/common.ts#L22
54
+ if value is None:
55
+ return value
56
+ elif isinstance(value, dict):
57
+ if value.get("value") is None:
58
+ raise ValueError(f"Expected a dictionary with a value key, but got {value}")
59
+ value = int(value["value"])
60
+ is_in_wei = value > 1e10
61
+ return value if is_in_wei else value * 10**18
62
+
63
+
64
+ SeerNormalizedWei = Annotated[Wei | None, BeforeValidator(seer_normalize_wei)]
65
+
66
+
67
+ class MarketId(BaseModel):
68
+ id: HexBytes
69
+
70
+
71
+ class SeerQuestion(BaseModel):
72
+ id: str
73
+ best_answer: HexBytes
74
+ finalize_ts: int
75
+
76
+
77
+ class SeerMarketQuestions(BaseModel):
78
+ question: SeerQuestion
79
+ market: MarketId
80
+
81
+
50
82
  class SeerMarket(BaseModel):
51
83
  model_config = ConfigDict(populate_by_name=True)
52
84
 
@@ -55,10 +87,11 @@ class SeerMarket(BaseModel):
55
87
  title: str = Field(alias="marketName")
56
88
  outcomes: t.Sequence[OutcomeStr]
57
89
  wrapped_tokens: list[HexAddress] = Field(alias="wrappedTokens")
58
- parent_outcome: int = Field(alias="parentOutcome")
59
- parent_market: t.Optional[SeerParentMarket] = Field(
60
- alias="parentMarket", default=None
90
+ parent_outcome: int = Field(
91
+ alias="parentOutcome", description="It comes as 0 from non-conditioned markets."
61
92
  )
93
+ parent_market: t.Optional["SeerMarket"] = Field(alias="parentMarket", default=None)
94
+ template_id: int = Field(alias="templateId")
62
95
  collateral_token: HexAddress = Field(alias="collateralToken")
63
96
  condition_id: HexBytes = Field(alias="conditionId")
64
97
  opening_ts: int = Field(alias="openingTs")
@@ -67,6 +100,8 @@ class SeerMarket(BaseModel):
67
100
  payout_reported: bool = Field(alias="payoutReported")
68
101
  payout_numerators: list[int] = Field(alias="payoutNumerators")
69
102
  outcomes_supply: int = Field(alias="outcomesSupply")
103
+ upper_bound: SeerNormalizedWei = Field(alias="upperBound", default=None)
104
+ lower_bound: SeerNormalizedWei = Field(alias="lowerBound", default=None)
70
105
 
71
106
  @property
72
107
  def has_valid_answer(self) -> bool:
@@ -105,11 +140,6 @@ class SeerMarket(BaseModel):
105
140
  for token in self.wrapped_tokens
106
141
  ]
107
142
 
108
- @property
109
- def is_binary(self) -> bool:
110
- # 3 because Seer has also third, `Invalid` outcome.
111
- return len(self.outcomes) == 3
112
-
113
143
  @property
114
144
  def collateral_token_contract_address_checksummed(self) -> ChecksumAddress:
115
145
  return Web3.to_checksum_address(self.collateral_token)
@@ -122,14 +152,78 @@ class SeerMarket(BaseModel):
122
152
  def created_time(self) -> DatetimeUTC:
123
153
  return DatetimeUTC.to_datetime_utc(self.block_timestamp)
124
154
 
155
+ @computed_field # type: ignore[prop-decorator]
125
156
  @property
126
157
  def url(self) -> str:
127
158
  chain_id = RPCConfig().chain_id
128
- return urljoin(SEER_BASE_URL, f"markets/{chain_id}/{self.id.hex()}")
159
+ return urljoin(SEER_BASE_URL, f"markets/{chain_id}/{self.id.to_0x_hex()}")
160
+
161
+
162
+ class SeerMarketWithQuestions(SeerMarket):
163
+ questions: list[SeerMarketQuestions]
164
+
165
+
166
+ class ExactInputSingleParams(BaseModel):
167
+ # from https://gnosisscan.io/address/0xffb643e73f280b97809a8b41f7232ab401a04ee1#code
168
+ model_config = ConfigDict(populate_by_name=True)
169
+ token_in: ChecksumAddress = Field(alias="tokenIn")
170
+ token_out: ChecksumAddress = Field(alias="tokenOut")
171
+ recipient: ChecksumAddress
172
+ deadline: int = Field(
173
+ default_factory=lambda: int((utcnow() + timedelta(minutes=10)).timestamp())
174
+ )
175
+ amount_in: Wei = Field(alias="amountIn")
176
+ amount_out_minimum: Wei = Field(alias="amountOutMinimum")
177
+ limit_sqrt_price: Wei = Field(
178
+ alias="limitSqrtPrice", default_factory=lambda: Wei(0)
179
+ ) # 0 for convenience, we also don't expect major price shifts
129
180
 
130
181
 
131
- class RedeemParams(BaseModel):
182
+ class QuoteExactInputSingleParams(BaseModel):
183
+ # from https://gnosisscan.io/address/0xcBaD9FDf0D2814659Eb26f600EFDeAF005Eda0F7#writeContract
132
184
  model_config = ConfigDict(populate_by_name=True)
133
- market: ChecksumAddress
134
- outcome_indices: list[int] = Field(alias="outcomeIndexes")
135
- amounts: list[OutcomeWei]
185
+ token_in: ChecksumAddress = Field(alias="tokenIn")
186
+ token_out: ChecksumAddress = Field(alias="tokenOut")
187
+ amount_in: Wei = Field(alias="amountIn")
188
+ limit_sqrt_price: Wei = Field(
189
+ alias="limitSqrtPrice", default_factory=lambda: Wei(0)
190
+ ) # 0 for convenience, we also don't expect major price shifts
191
+
192
+
193
+ class SeerTransactionType(str, Enum):
194
+ SWAP = "swap"
195
+ SPLIT = "split"
196
+
197
+
198
+ class SeerTransaction(BaseModel):
199
+ model_config = ConfigDict(populate_by_name=True)
200
+
201
+ market_name: str = Field(alias="marketName")
202
+ market_id: HexBytes = Field(alias="marketId")
203
+ type: SeerTransactionType
204
+ block_number: int = Field(alias="blockNumber")
205
+ transaction_hash: HexBytes = Field(alias="transactionHash")
206
+ collateral: HexAddress
207
+ collateral_symbol: str = Field(alias="collateralSymbol")
208
+
209
+ token_in: HexAddress = Field(alias="tokenIn")
210
+ token_out: HexAddress = Field(alias="tokenOut")
211
+ amount_in: Wei = Field(alias="amountIn")
212
+ amount_out: Wei = Field(alias="amountOut")
213
+ token_in_symbol: str = Field(alias="tokenInSymbol")
214
+ token_out_symbol: str = Field(alias="tokenOutSymbol")
215
+ timestamp: int | None = None
216
+
217
+ amount: Wei | None = None
218
+
219
+ @property
220
+ def timestamp_dt(self) -> DatetimeUTC | None:
221
+ return DatetimeUTC.to_datetime_utc(self.timestamp) if self.timestamp else None
222
+
223
+ @property
224
+ def token_in_checksum(self) -> ChecksumAddress:
225
+ return Web3.to_checksum_address(self.token_in)
226
+
227
+ @property
228
+ def token_out_checksum(self) -> ChecksumAddress:
229
+ return Web3.to_checksum_address(self.token_out)
@@ -0,0 +1,2 @@
1
+ class PriceCalculationError(Exception):
2
+ """Raised when there's an error discovering prices for a market."""
@@ -1,46 +1,38 @@
1
- import typing as t
2
-
3
1
  from cachetools import TTLCache, cached
2
+ from pydantic import BaseModel
4
3
  from web3 import Web3
5
4
 
5
+ from prediction_market_agent_tooling.deploy.constants import is_invalid_outcome
6
6
  from prediction_market_agent_tooling.gtypes import (
7
7
  ChecksumAddress,
8
8
  CollateralToken,
9
9
  HexAddress,
10
10
  OutcomeStr,
11
+ OutcomeToken,
11
12
  Probability,
13
+ Wei,
12
14
  )
13
15
  from prediction_market_agent_tooling.loggers import logger
14
16
  from prediction_market_agent_tooling.markets.seer.data_models import SeerMarket
17
+ from prediction_market_agent_tooling.markets.seer.exceptions import (
18
+ PriceCalculationError,
19
+ )
20
+ from prediction_market_agent_tooling.markets.seer.seer_contracts import (
21
+ QuoteExactInputSingleParams,
22
+ SwaprQuoterContract,
23
+ )
15
24
  from prediction_market_agent_tooling.markets.seer.seer_subgraph_handler import (
16
25
  SeerSubgraphHandler,
17
26
  )
18
- from prediction_market_agent_tooling.markets.seer.subgraph_data_models import SeerPool
19
27
  from prediction_market_agent_tooling.tools.cow.cow_order import (
20
28
  get_buy_token_amount_else_raise,
21
29
  )
22
30
  from prediction_market_agent_tooling.tools.hexbytes_custom import HexBytes
23
31
 
24
32
 
25
- def _make_cache_key(
26
- *args: t.Any,
27
- token: ChecksumAddress,
28
- collateral_exchange_amount: CollateralToken | None = None,
29
- ) -> str:
30
- """
31
- Generate a unique cache key based on a token address and optional collateral token.
32
- """
33
-
34
- if collateral_exchange_amount is None:
35
- return f"{token}-no_collateral"
36
-
37
- return "-".join(
38
- [
39
- token,
40
- collateral_exchange_amount.symbol,
41
- str(collateral_exchange_amount.value),
42
- ]
43
- )
33
+ class Prices(BaseModel):
34
+ priceOfCollateralInAskingToken: CollateralToken
35
+ priceOfAskingTokenInCollateral: CollateralToken
44
36
 
45
37
 
46
38
  class PriceManager:
@@ -61,20 +53,20 @@ class PriceManager:
61
53
  price_diff_pct = abs(old_price - normalized_price) / max(old_price, 0.01)
62
54
  if price_diff_pct > max_price_diff:
63
55
  logger.info(
64
- f"{price_diff_pct=} larger than {max_price_diff=} for seer market {self.seer_market.id.hex()} "
56
+ f"{price_diff_pct=} larger than {max_price_diff=} for seer market {self.seer_market.id.to_0x_hex()} "
65
57
  )
66
58
 
67
- @cached(TTLCache(maxsize=100, ttl=5 * 60), key=_make_cache_key)
68
- def get_price_for_token(
59
+ def get_price_for_token(self, token: ChecksumAddress) -> CollateralToken | None:
60
+ return self.get_amount_of_token_in_collateral(token, OutcomeToken(1))
61
+
62
+ @cached(TTLCache(maxsize=100, ttl=5 * 60))
63
+ def get_amount_of_collateral_in_token(
69
64
  self,
70
65
  token: ChecksumAddress,
71
- collateral_exchange_amount: CollateralToken | None = None,
72
- ) -> CollateralToken | None:
73
- collateral_exchange_amount = (
74
- collateral_exchange_amount
75
- if collateral_exchange_amount is not None
76
- else CollateralToken(1)
77
- )
66
+ collateral_exchange_amount: CollateralToken,
67
+ ) -> OutcomeToken | None:
68
+ if token == self.seer_market.collateral_token_contract_address_checksummed:
69
+ return OutcomeToken(collateral_exchange_amount.value)
78
70
 
79
71
  try:
80
72
  buy_token_amount = get_buy_token_amount_else_raise(
@@ -82,23 +74,55 @@ class PriceManager:
82
74
  sell_token=self.seer_market.collateral_token_contract_address_checksummed,
83
75
  buy_token=token,
84
76
  )
85
- price = collateral_exchange_amount.as_wei / buy_token_amount
86
- return CollateralToken(price)
77
+ return OutcomeToken.from_token(buy_token_amount.as_token)
87
78
 
88
79
  except Exception as e:
89
80
  logger.warning(
90
81
  f"Could not get quote for {token=} from Cow, exception {e=}. Falling back to pools. "
91
82
  )
92
- return self.get_token_price_from_pools(token=token)
83
+ quote = self.get_swapr_input_quote(
84
+ input_token=self.seer_market.collateral_token_contract_address_checksummed,
85
+ output_token=token,
86
+ input_amount=collateral_exchange_amount.as_wei,
87
+ )
88
+ return OutcomeToken.from_token(quote.as_token)
93
89
 
94
- @staticmethod
95
- def pool_token0_matches_token(token: ChecksumAddress, pool: SeerPool) -> bool:
96
- return pool.token0.id.hex().lower() == token.lower()
90
+ @cached(TTLCache(maxsize=100, ttl=5 * 60))
91
+ def get_amount_of_token_in_collateral(
92
+ self,
93
+ token: ChecksumAddress,
94
+ token_exchange_amount: OutcomeToken,
95
+ ) -> CollateralToken | None:
96
+ if token == self.seer_market.collateral_token_contract_address_checksummed:
97
+ return token_exchange_amount.as_token
98
+
99
+ try:
100
+ buy_collateral_amount = get_buy_token_amount_else_raise(
101
+ sell_amount=token_exchange_amount.as_outcome_wei.as_wei,
102
+ sell_token=token,
103
+ buy_token=self.seer_market.collateral_token_contract_address_checksummed,
104
+ )
105
+ return buy_collateral_amount.as_token
106
+
107
+ except Exception as e:
108
+ logger.warning(
109
+ f"Could not get quote for {token=} from Cow, exception {e=}. Falling back to pools. "
110
+ )
111
+ quote = self.get_swapr_input_quote(
112
+ input_token=token,
113
+ output_token=self.seer_market.collateral_token_contract_address_checksummed,
114
+ input_amount=token_exchange_amount.as_outcome_wei.as_wei,
115
+ )
116
+ return quote.as_token
97
117
 
98
118
  def get_token_price_from_pools(
99
119
  self,
100
120
  token: ChecksumAddress,
101
- ) -> CollateralToken | None:
121
+ ) -> Prices | None:
122
+ """
123
+ Although this might come handy,
124
+ consider using `get_amount_of_collateral_in_token` or `get_amount_of_token_in_collateral` to have an exact quote.
125
+ """
102
126
  pool = SeerSubgraphHandler().get_pool_by_token(
103
127
  token_address=token,
104
128
  collateral_address=self.seer_market.collateral_token_contract_address_checksummed,
@@ -108,15 +132,24 @@ class PriceManager:
108
132
  logger.warning(f"Could not find a pool for {token=}")
109
133
  return None
110
134
 
111
- # The mapping below is odd but surprisingly the Algebra subgraph delivers the token1Price
112
- # for the token0 and the token0Price for the token1 pool.
113
- # For example, in a outcomeYES (token0)/sDAI pool (token1), token1Price is the price of outcomeYES in units of sDAI.
114
- price = (
115
- pool.token1Price
116
- if self.pool_token0_matches_token(token=token, pool=pool)
117
- else pool.token0Price
135
+ if (
136
+ Web3.to_checksum_address(pool.token0.id)
137
+ == self.seer_market.collateral_token_contract_address_checksummed
138
+ ):
139
+ price_coll_in_asking = (
140
+ pool.token1Price
141
+ ) # how many outcome tokens per 1 collateral
142
+ price_asking_in_coll = (
143
+ pool.token0Price
144
+ ) # how many collateral tokens per 1 outcome
145
+ else:
146
+ price_coll_in_asking = pool.token0Price
147
+ price_asking_in_coll = pool.token1Price
148
+
149
+ return Prices(
150
+ priceOfCollateralInAskingToken=price_coll_in_asking,
151
+ priceOfAskingTokenInCollateral=price_asking_in_coll,
118
152
  )
119
- return price
120
153
 
121
154
  def build_probability_map(self) -> dict[OutcomeStr, Probability]:
122
155
  # Inspired by https://github.com/seer-pm/demo/blob/ca682153a6b4d4dd3dcc4ad8bdcbe32202fc8fe7/web/src/hooks/useMarketOdds.ts#L15
@@ -128,7 +161,7 @@ class PriceManager:
128
161
  )
129
162
  # It's okay if invalid (last) outcome has price 0, but not the other outcomes.
130
163
  if price is None and idx != len(self.seer_market.wrapped_tokens) - 1:
131
- raise ValueError(
164
+ raise PriceCalculationError(
132
165
  f"Couldn't get price for {wrapped_token} for market {self.seer_market.url}."
133
166
  )
134
167
  price_data[wrapped_token] = (
@@ -142,7 +175,7 @@ class PriceManager:
142
175
  sum(price_data.values(), start=CollateralToken.zero())
143
176
  == CollateralToken.zero()
144
177
  ):
145
- raise ValueError(
178
+ raise PriceCalculationError(
146
179
  f"All prices for market {self.seer_market.url} are zero. This shouldn't happen."
147
180
  )
148
181
 
@@ -157,6 +190,88 @@ class PriceManager:
157
190
  outcome = self.seer_market.outcomes[
158
191
  self.seer_market.wrapped_tokens.index(outcome_token)
159
192
  ]
160
- normalized_prices[OutcomeStr(outcome)] = new_price
193
+ normalized_prices[outcome] = new_price
161
194
 
162
195
  return normalized_prices
196
+
197
+ def build_initial_probs_from_pool(
198
+ self, model: SeerMarket, wrapped_tokens: list[ChecksumAddress]
199
+ ) -> tuple[dict[OutcomeStr, Probability], dict[OutcomeStr, OutcomeToken]]:
200
+ """
201
+ Builds a map of outcome to probability and outcome token pool.
202
+ """
203
+ probability_map = {}
204
+ outcome_token_pool = {}
205
+ wrapped_tokens_with_supply = [
206
+ (
207
+ token,
208
+ SeerSubgraphHandler().get_pool_by_token(
209
+ token, model.collateral_token_contract_address_checksummed
210
+ ),
211
+ )
212
+ for token in wrapped_tokens
213
+ ]
214
+ wrapped_tokens_with_supply = [
215
+ (token, pool)
216
+ for token, pool in wrapped_tokens_with_supply
217
+ if pool is not None
218
+ ]
219
+
220
+ for token, pool in wrapped_tokens_with_supply:
221
+ if pool is None or pool.token1.id is None or pool.token0.id is None:
222
+ continue
223
+ if HexBytes(token) == HexBytes(pool.token1.id):
224
+ outcome_token_pool[
225
+ OutcomeStr(model.outcomes[wrapped_tokens.index(token)])
226
+ ] = (
227
+ OutcomeToken(pool.totalValueLockedToken0)
228
+ if pool.totalValueLockedToken0 is not None
229
+ else OutcomeToken(0)
230
+ )
231
+ probability_map[
232
+ OutcomeStr(model.outcomes[wrapped_tokens.index(token)])
233
+ ] = Probability(pool.token0Price.value)
234
+ else:
235
+ outcome_token_pool[
236
+ OutcomeStr(model.outcomes[wrapped_tokens.index(token)])
237
+ ] = (
238
+ OutcomeToken(pool.totalValueLockedToken1)
239
+ if pool.totalValueLockedToken1 is not None
240
+ else OutcomeToken(0)
241
+ )
242
+ probability_map[
243
+ OutcomeStr(model.outcomes[wrapped_tokens.index(token)])
244
+ ] = Probability(pool.token1Price.value)
245
+
246
+ for outcome in model.outcomes:
247
+ if outcome not in outcome_token_pool:
248
+ outcome_token_pool[outcome] = OutcomeToken(0)
249
+ logger.warning(
250
+ f"Outcome {outcome} not found in outcome_token_pool for market {self.seer_market.url}."
251
+ )
252
+ if outcome not in probability_map:
253
+ if not is_invalid_outcome(outcome):
254
+ raise PriceCalculationError(
255
+ f"Couldn't get probability for {outcome} for market {self.seer_market.url}."
256
+ )
257
+ else:
258
+ probability_map[outcome] = Probability(0)
259
+ return probability_map, outcome_token_pool
260
+
261
+ def get_swapr_input_quote(
262
+ self,
263
+ input_token: ChecksumAddress,
264
+ output_token: ChecksumAddress,
265
+ input_amount: Wei,
266
+ web3: Web3 | None = None,
267
+ ) -> Wei: # Not marked as OutcomeWei, but this works for both buying and selling.
268
+ quoter = SwaprQuoterContract()
269
+ amount_out, _ = quoter.quote_exact_input_single(
270
+ QuoteExactInputSingleParams(
271
+ token_in=input_token,
272
+ token_out=output_token,
273
+ amount_in=input_amount,
274
+ ),
275
+ web3=web3,
276
+ )
277
+ return amount_out