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
@@ -5,3 +5,9 @@ INVALID_OUTCOME_LOWERCASE_IDENTIFIER = "invalid"
5
5
  # Market-agnostic outcome identifiers
6
6
  YES_OUTCOME_LOWERCASE_IDENTIFIER = "yes"
7
7
  NO_OUTCOME_LOWERCASE_IDENTIFIER = "no"
8
+ UP_OUTCOME_LOWERCASE_IDENTIFIER = "up"
9
+ DOWN_OUTCOME_LOWERCASE_IDENTIFIER = "down"
10
+
11
+
12
+ def is_invalid_outcome(outcome: str) -> bool:
13
+ return INVALID_OUTCOME_LOWERCASE_IDENTIFIER in outcome.lower()
@@ -1,6 +1,6 @@
1
1
  import typing as t
2
2
  from decimal import Decimal
3
- from typing import NewType
3
+ from typing import Annotated, NewType, TypeAlias
4
4
 
5
5
  from eth_typing.evm import ( # noqa: F401 # Import for the sake of easy importing with others from here.
6
6
  Address,
@@ -8,6 +8,7 @@ from eth_typing.evm import ( # noqa: F401 # Import for the sake of easy import
8
8
  HexAddress,
9
9
  HexStr,
10
10
  )
11
+ from pydantic import BeforeValidator
11
12
  from pydantic.types import SecretStr
12
13
  from pydantic.v1.types import SecretStr as SecretStrV1
13
14
  from web3 import Web3
@@ -18,6 +19,7 @@ from web3.types import ( # noqa: F401 # Import for the sake of easy importing
18
19
  )
19
20
  from web3.types import Wei as Web3Wei
20
21
 
22
+ from prediction_market_agent_tooling.gtypes import ChecksumAddress
21
23
  from prediction_market_agent_tooling.tools._generic_value import _GenericValue
22
24
  from prediction_market_agent_tooling.tools.datetime_utc import ( # noqa: F401 # Import for the sake of easy importing with others from here.
23
25
  DatetimeUTC,
@@ -26,6 +28,14 @@ from prediction_market_agent_tooling.tools.hexbytes_custom import ( # noqa: F40
26
28
  HexBytes,
27
29
  )
28
30
 
31
+ VerifiedChecksumAddress: TypeAlias = Annotated[
32
+ ChecksumAddress, BeforeValidator(Web3.to_checksum_address)
33
+ ]
34
+ VerifiedChecksumAddressOrNone: TypeAlias = Annotated[
35
+ ChecksumAddress | None,
36
+ BeforeValidator(lambda x: Web3.to_checksum_address(x) if x else None),
37
+ ]
38
+
29
39
 
30
40
  class CollateralToken(_GenericValue[int | float | str | Decimal, float], parser=float):
31
41
  """
@@ -9,7 +9,7 @@ from prediction_market_agent_tooling.deploy.betting_strategy import (
9
9
  from prediction_market_agent_tooling.gtypes import USD, Probability
10
10
  from prediction_market_agent_tooling.markets.agent_market import (
11
11
  AgentMarket,
12
- ProcessedTradedMarket,
12
+ ProcessedMarket,
13
13
  )
14
14
  from prediction_market_agent_tooling.markets.omen.data_models import (
15
15
  OMEN_FALSE_OUTCOME,
@@ -64,7 +64,7 @@ class JobAgentMarket(AgentMarket, ABC):
64
64
  @abstractmethod
65
65
  def submit_job_result(
66
66
  self, agent_name: str, max_bond: USD, result: str
67
- ) -> ProcessedTradedMarket:
67
+ ) -> ProcessedMarket:
68
68
  """Submit the completed result for this job."""
69
69
 
70
70
  def to_simple_job(self, max_bond: USD) -> SimpleJob:
@@ -1,13 +1,9 @@
1
1
  import typing as t
2
2
 
3
3
  from prediction_market_agent_tooling.config import APIKeys
4
- from prediction_market_agent_tooling.deploy.betting_strategy import (
5
- KellyBettingStrategy,
6
- TradeType,
7
- )
8
4
  from prediction_market_agent_tooling.gtypes import USD
9
5
  from prediction_market_agent_tooling.jobs.jobs_models import JobAgentMarket
10
- from prediction_market_agent_tooling.markets.agent_market import ProcessedTradedMarket
6
+ from prediction_market_agent_tooling.markets.agent_market import ProcessedMarket
11
7
  from prediction_market_agent_tooling.markets.data_models import PlacedTrade, Trade
12
8
  from prediction_market_agent_tooling.markets.omen.omen import (
13
9
  OmenAgentMarket,
@@ -72,7 +68,7 @@ class OmenJobAgentMarket(OmenAgentMarket, JobAgentMarket):
72
68
 
73
69
  def submit_job_result(
74
70
  self, agent_name: str, max_bond: USD, result: str
75
- ) -> ProcessedTradedMarket:
71
+ ) -> ProcessedMarket:
76
72
  if not APIKeys().enable_ipfs_upload:
77
73
  raise RuntimeError(
78
74
  f"ENABLE_IPFS_UPLOAD must be set to True to upload job results."
@@ -81,7 +77,7 @@ class OmenJobAgentMarket(OmenAgentMarket, JobAgentMarket):
81
77
  trade = self.get_job_trade(max_bond, result)
82
78
  buy_id = self.buy_tokens(outcome=trade.outcome, amount=trade.amount)
83
79
 
84
- processed_traded_market = ProcessedTradedMarket(
80
+ processed_traded_market = ProcessedMarket(
85
81
  answer=self.get_job_answer(result),
86
82
  trades=[PlacedTrade.from_trade(trade, id=buy_id)],
87
83
  )
@@ -92,20 +88,21 @@ class OmenJobAgentMarket(OmenAgentMarket, JobAgentMarket):
92
88
  return processed_traded_market
93
89
 
94
90
  def get_job_trade(self, max_bond: USD, result: str) -> Trade:
91
+ raise NotImplementedError("TODO: Refactor to avoid circular imports.")
95
92
  # Because jobs are powered by prediction markets, potentional reward depends on job's liquidity and our will to bond (bet) our xDai into our job completion.
96
- strategy = KellyBettingStrategy(max_bet_amount=max_bond)
97
- required_trades = strategy.calculate_trades(
98
- existing_position=None,
99
- answer=self.get_job_answer(result),
100
- market=self,
101
- )
102
- assert (
103
- len(required_trades) == 1
104
- ), f"Shouldn't process same job twice: {required_trades}"
105
- trade = required_trades[0]
106
- assert trade.trade_type == TradeType.BUY, "Should only buy on job markets."
107
- assert trade.outcome, "Should buy only YES on job markets."
108
- return required_trades[0]
93
+ # strategy = FullBinaryKellyBettingStrategy(max_position_amount=max_bond)
94
+ # required_trades = strategy.calculate_trades(
95
+ # existing_position=None,
96
+ # answer=self.get_job_answer(result),
97
+ # market=self,
98
+ # )
99
+ # assert (
100
+ # len(required_trades) == 1
101
+ # ), f"Shouldn't process same job twice: {required_trades}"
102
+ # trade = required_trades[0]
103
+ # assert trade.trade_type == TradeType.BUY, "Should only buy on job markets."
104
+ # assert trade.outcome, "Should buy only YES on job markets."
105
+ # return required_trades[0]
109
106
 
110
107
  @staticmethod
111
108
  def from_omen_market(market: OmenMarket) -> "OmenJobAgentMarket":
@@ -133,4 +130,6 @@ class OmenJobAgentMarket(OmenAgentMarket, JobAgentMarket):
133
130
  condition=market.condition,
134
131
  finalized_time=market.finalized_time,
135
132
  fees=market.fees,
133
+ upper_bound=market.upper_bound,
134
+ lower_bound=market.lower_bound,
136
135
  )
@@ -10,6 +10,8 @@ from pydantic_settings import BaseSettings, SettingsConfigDict
10
10
  from pythonjsonlogger import jsonlogger
11
11
  from tenacity import RetryError
12
12
 
13
+ UNPATCHED_PRINT_FN = builtins.print
14
+
13
15
 
14
16
  class LogFormat(str, Enum):
15
17
  DEFAULT = "default"
@@ -153,7 +155,13 @@ def print_using_logger_info(
153
155
  end: str = "\n",
154
156
  **kwargs: t.Any,
155
157
  ) -> None:
156
- logger.info(sep.join(map(str, values)) + end)
158
+ # If `logger.exception` is used, loguru+traceback somehow uses `print` statement to format the error stack,
159
+ # if that happens, without this if condition, it errors out because of deadlock and/or recursion errors.
160
+ # This is hacky, but that's exactly how loguru is checking for it internally..
161
+ if any(getattr(handler._lock_acquired, "acquired", False) for handler in logger._core.handlers.values()): # type: ignore # They use stubs and didn't type this.
162
+ UNPATCHED_PRINT_FN(*values, sep=sep, end=end, **kwargs)
163
+ else:
164
+ logger.info(sep.join(map(str, values)) + end)
157
165
 
158
166
 
159
167
  patch_logger()
@@ -61,7 +61,8 @@ class LogprobsParser:
61
61
  (
62
62
  i
63
63
  for i in range(result_start_index, len(logprobs))
64
- if logprobs[i]["token"] in {",", '"', ",\n", "\",\n'", '",\n'}
64
+ if logprobs[i]["token"]
65
+ in {",", '"', ",\n", "\",\n'", '",\n', '"\n', "\n"}
65
66
  ),
66
67
  len(logprobs) - 1,
67
68
  )
@@ -1,4 +1,5 @@
1
1
  import typing as t
2
+ from datetime import timedelta
2
3
  from enum import Enum
3
4
  from math import prod
4
5
 
@@ -10,17 +11,20 @@ from web3 import Web3
10
11
  from prediction_market_agent_tooling.benchmark.utils import get_most_probable_outcome
11
12
  from prediction_market_agent_tooling.config import APIKeys
12
13
  from prediction_market_agent_tooling.deploy.constants import (
13
- INVALID_OUTCOME_LOWERCASE_IDENTIFIER,
14
+ DOWN_OUTCOME_LOWERCASE_IDENTIFIER,
14
15
  NO_OUTCOME_LOWERCASE_IDENTIFIER,
16
+ UP_OUTCOME_LOWERCASE_IDENTIFIER,
15
17
  YES_OUTCOME_LOWERCASE_IDENTIFIER,
18
+ is_invalid_outcome,
16
19
  )
17
20
  from prediction_market_agent_tooling.gtypes import (
18
21
  OutcomeStr,
19
22
  OutcomeToken,
20
23
  OutcomeWei,
21
24
  Probability,
25
+ Wei,
26
+ xDai,
22
27
  )
23
- from prediction_market_agent_tooling.loggers import logger
24
28
  from prediction_market_agent_tooling.markets.data_models import (
25
29
  USD,
26
30
  Bet,
@@ -41,9 +45,6 @@ from prediction_market_agent_tooling.tools.utils import (
41
45
 
42
46
  class ProcessedMarket(BaseModel):
43
47
  answer: CategoricalProbabilisticAnswer
44
-
45
-
46
- class ProcessedTradedMarket(ProcessedMarket):
47
48
  trades: list[PlacedTrade]
48
49
 
49
50
 
@@ -61,6 +62,24 @@ class FilterBy(str, Enum):
61
62
  NONE = "none"
62
63
 
63
64
 
65
+ class ParentMarket(BaseModel):
66
+ market: "AgentMarket"
67
+ parent_outcome: int
68
+
69
+
70
+ class QuestionType(str, Enum):
71
+ ALL = "all"
72
+ CATEGORICAL = "categorical"
73
+ SCALAR = "scalar"
74
+ BINARY = "binary"
75
+
76
+
77
+ class ConditionalFilterType(Enum):
78
+ ALL = 1
79
+ ONLY_CONDITIONAL = 2
80
+ ONLY_NOT_CONDITIONAL = 3
81
+
82
+
64
83
  class AgentMarket(BaseModel):
65
84
  """
66
85
  Common market class that can be created from vendor specific markets.
@@ -83,6 +102,11 @@ class AgentMarket(BaseModel):
83
102
  volume: CollateralToken | None
84
103
  fees: MarketFees
85
104
 
105
+ upper_bound: Wei | None = None
106
+ lower_bound: Wei | None = None
107
+
108
+ parent: ParentMarket | None = None
109
+
86
110
  @field_validator("probabilities")
87
111
  def validate_probabilities(
88
112
  cls,
@@ -92,11 +116,6 @@ class AgentMarket(BaseModel):
92
116
  outcomes: t.Sequence[OutcomeStr] = check_not_none(info.data.get("outcomes"))
93
117
  if set(probs.keys()) != set(outcomes):
94
118
  raise ValueError("Keys of `probabilities` must match `outcomes` exactly.")
95
- total = float(sum(probs.values()))
96
- if not 0.999 <= total <= 1.001:
97
- # We simply log a warning because for some use-cases (e.g. existing positions), the
98
- # markets might be already closed hence no reliable outcome token prices exist anymore.
99
- logger.warning(f"Probabilities for market {info.data=} do not sum to 1.")
100
119
  return probs
101
120
 
102
121
  @field_validator("outcome_token_pool")
@@ -115,6 +134,9 @@ class AgentMarket(BaseModel):
115
134
  )
116
135
  return outcome_token_pool
117
136
 
137
+ def have_bet_on_market_since(self, keys: APIKeys, since: timedelta) -> bool:
138
+ raise NotImplementedError("Subclasses must implement this method")
139
+
118
140
  def get_outcome_token_pool_by_outcome(self, outcome: OutcomeStr) -> OutcomeToken:
119
141
  if self.outcome_token_pool is None or not self.outcome_token_pool:
120
142
  return OutcomeToken(0)
@@ -129,6 +151,23 @@ class AgentMarket(BaseModel):
129
151
  if "fees" not in data and "fee" in data:
130
152
  data["fees"] = MarketFees(absolute=0.0, bet_proportion=data["fee"])
131
153
  del data["fee"]
154
+ # Backward compatibility for older `AgentMarket` without `probabilities`.
155
+ if "probabilities" not in data and "current_p_yes" in data:
156
+ yes_outcome = data["outcomes"][
157
+ [o.lower() for o in data["outcomes"]].index(
158
+ YES_OUTCOME_LOWERCASE_IDENTIFIER
159
+ )
160
+ ]
161
+ no_outcome = data["outcomes"][
162
+ [o.lower() for o in data["outcomes"]].index(
163
+ NO_OUTCOME_LOWERCASE_IDENTIFIER
164
+ )
165
+ ]
166
+ data["probabilities"] = {
167
+ yes_outcome: data["current_p_yes"],
168
+ no_outcome: 1 - data["current_p_yes"],
169
+ }
170
+ del data["current_p_yes"]
132
171
  return data
133
172
 
134
173
  def market_outcome_for_probability_key(
@@ -149,6 +188,17 @@ class AgentMarket(BaseModel):
149
188
  f"Could not find probability for market outcome {market_outcome}"
150
189
  )
151
190
 
191
+ @property
192
+ def question_type(self) -> QuestionType:
193
+ if self.is_binary:
194
+ return QuestionType.BINARY
195
+
196
+ elif self.is_scalar:
197
+ return QuestionType.SCALAR
198
+
199
+ else:
200
+ return QuestionType.CATEGORICAL
201
+
152
202
  @property
153
203
  def is_binary(self) -> bool:
154
204
  # 3 outcomes can also be binary if 3rd outcome is invalid (Seer)
@@ -162,11 +212,39 @@ class AgentMarket(BaseModel):
162
212
 
163
213
  if len(lowercase_outcomes) == 3:
164
214
  invalid_outcome = lowercase_outcomes[-1]
165
- has_invalid = INVALID_OUTCOME_LOWERCASE_IDENTIFIER in invalid_outcome
215
+ has_invalid = is_invalid_outcome(invalid_outcome)
166
216
  return has_yes and has_no and has_invalid
167
217
 
168
218
  return has_yes and has_no
169
219
 
220
+ @property
221
+ def is_scalar(self) -> bool:
222
+ # 3 outcomes can also be binary if 3rd outcome is invalid (Seer)
223
+ if len(self.outcomes) not in [2, 3]:
224
+ return False
225
+
226
+ lowercase_outcomes = [outcome.lower() for outcome in self.outcomes]
227
+
228
+ has_up = UP_OUTCOME_LOWERCASE_IDENTIFIER in lowercase_outcomes
229
+ has_down = DOWN_OUTCOME_LOWERCASE_IDENTIFIER in lowercase_outcomes
230
+
231
+ if len(lowercase_outcomes) == 3:
232
+ invalid_outcome = lowercase_outcomes[-1]
233
+ has_invalid = is_invalid_outcome(invalid_outcome)
234
+ return has_up and has_down and has_invalid
235
+
236
+ return has_up and has_down
237
+
238
+ @property
239
+ def p_up(self) -> Probability:
240
+ probs_lowercase = {o.lower(): p for o, p in self.probabilities.items()}
241
+ return check_not_none(probs_lowercase.get(UP_OUTCOME_LOWERCASE_IDENTIFIER))
242
+
243
+ @property
244
+ def p_down(self) -> Probability:
245
+ probs_lowercase = {o.lower(): p for o, p in self.probabilities.items()}
246
+ return check_not_none(probs_lowercase.get(DOWN_OUTCOME_LOWERCASE_IDENTIFIER))
247
+
170
248
  @property
171
249
  def p_yes(self) -> Probability:
172
250
  probs_lowercase = {o.lower(): p for o, p in self.probabilities.items()}
@@ -337,7 +415,8 @@ class AgentMarket(BaseModel):
337
415
  filter_by: FilterBy = FilterBy.OPEN,
338
416
  created_after: t.Optional[DatetimeUTC] = None,
339
417
  excluded_questions: set[str] | None = None,
340
- fetch_categorical_markets: bool = False,
418
+ question_type: QuestionType = QuestionType.ALL,
419
+ conditional_filter_type: ConditionalFilterType = ConditionalFilterType.ONLY_NOT_CONDITIONAL,
341
420
  ) -> t.Sequence["AgentMarket"]:
342
421
  raise NotImplementedError("Subclasses must implement this method")
343
422
 
@@ -380,7 +459,7 @@ class AgentMarket(BaseModel):
380
459
 
381
460
  def store_trades(
382
461
  self,
383
- traded_market: ProcessedTradedMarket | None,
462
+ traded_market: ProcessedMarket | None,
384
463
  keys: APIKeys,
385
464
  agent_name: str,
386
465
  web3: Web3 | None = None,
@@ -439,11 +518,20 @@ class AgentMarket(BaseModel):
439
518
  )
440
519
 
441
520
  def get_outcome_index(self, outcome: OutcomeStr) -> int:
442
- outcomes_lowercase = [o.lower() for o in self.outcomes]
521
+ """Get the index of the given outcome in the market's outcomes."""
443
522
  try:
444
- return outcomes_lowercase.index(outcome.lower())
445
- except ValueError:
446
- raise ValueError(f"Outcome `{outcome}` not found in `{self.outcomes}`.")
523
+ return [o.lower() for o in self.outcomes].index(outcome.lower())
524
+ except ValueError as e:
525
+ raise ValueError(
526
+ f"Outcome '{outcome}' not found in market outcomes: {self.outcomes}"
527
+ ) from e
528
+
529
+ def ensure_min_native_balance(
530
+ self,
531
+ min_required_balance: xDai,
532
+ multiplier: float = 3.0,
533
+ ) -> None:
534
+ raise NotImplementedError("Subclass must implement this method")
447
535
 
448
536
  def get_token_balance(self, user_id: str, outcome: OutcomeStr) -> OutcomeToken:
449
537
  raise NotImplementedError("Subclasses must implement this method")
@@ -492,7 +580,7 @@ class AgentMarket(BaseModel):
492
580
 
493
581
  @staticmethod
494
582
  def get_user_id(api_keys: APIKeys) -> str:
495
- raise NotImplementedError("Subclasses must implement this method")
583
+ return api_keys.bet_from_address
496
584
 
497
585
  def get_most_recent_trade_datetime(self, user_id: str) -> DatetimeUTC | None:
498
586
  raise NotImplementedError("Subclasses must implement this method")
@@ -1,28 +1,34 @@
1
+ from typing import Sequence
2
+
1
3
  from web3 import Web3
2
4
  from web3.constants import HASH_ZERO
3
5
 
4
6
  from prediction_market_agent_tooling.config import APIKeys
5
- from prediction_market_agent_tooling.gtypes import HexBytes, HexStr
7
+ from prediction_market_agent_tooling.gtypes import (
8
+ ChecksumAddress,
9
+ HexBytes,
10
+ HexStr,
11
+ OutcomeStr,
12
+ )
6
13
  from prediction_market_agent_tooling.loggers import logger
7
- from prediction_market_agent_tooling.markets.agent_market import ProcessedTradedMarket
14
+ from prediction_market_agent_tooling.markets.agent_market import ProcessedMarket
8
15
  from prediction_market_agent_tooling.markets.omen.data_models import (
9
16
  ContractPrediction,
10
17
  IPFSAgentResult,
11
18
  )
12
19
  from prediction_market_agent_tooling.markets.omen.omen_contracts import (
13
- OmenAgentResultMappingContract,
20
+ _AgentResultMappingContract,
14
21
  )
15
22
  from prediction_market_agent_tooling.tools.ipfs.ipfs_handler import IPFSHandler
16
23
  from prediction_market_agent_tooling.tools.utils import BPS_CONSTANT
17
24
  from prediction_market_agent_tooling.tools.web3_utils import ipfscidv0_to_byte32
18
25
 
19
- # max uint16 for easy prediction identification (if market does not have YES outcome)
20
- UINT16_MAX = 2**16 - 1 # = 65535
21
-
22
26
 
23
27
  def store_trades(
24
- market_id: str,
25
- traded_market: ProcessedTradedMarket | None,
28
+ contract: _AgentResultMappingContract,
29
+ market_id: ChecksumAddress,
30
+ outcomes: Sequence[OutcomeStr],
31
+ traded_market: ProcessedMarket | None,
26
32
  keys: APIKeys,
27
33
  agent_name: str,
28
34
  web3: Web3 | None = None,
@@ -31,10 +37,18 @@ def store_trades(
31
37
  logger.warning(f"No prediction for market {market_id}, not storing anything.")
32
38
  return None
33
39
 
34
- yes_probability = traded_market.answer.get_yes_probability()
35
- if not yes_probability:
36
- logger.info("Skipping this since no yes_probability available.")
40
+ logger.info(
41
+ f"Storing trades for market {market_id}, with outcomes {outcomes}, {traded_market=}."
42
+ )
43
+
44
+ probabilities = traded_market.answer.probabilities
45
+ if not probabilities:
46
+ logger.info("Skipping this since no probabilities available.")
37
47
  return None
48
+
49
+ if all(outcome not in probabilities for outcome in outcomes):
50
+ raise ValueError("No of the market's outcomes is in the probabilities.")
51
+
38
52
  reasoning = traded_market.answer.reasoning if traded_market.answer.reasoning else ""
39
53
 
40
54
  ipfs_hash_decoded = HexBytes(HASH_ZERO)
@@ -46,24 +60,28 @@ def store_trades(
46
60
  ipfs_hash_decoded = ipfscidv0_to_byte32(ipfs_hash)
47
61
 
48
62
  # tx_hashes must be list of bytes32 (see Solidity contract).
49
- tx_hashes = [
50
- HexBytes(HexStr(i.id)) for i in traded_market.trades if i.id is not None
51
- ]
63
+ tx_hashes = [HexBytes(HexStr(i.id)) for i in traded_market.trades]
52
64
 
53
- estimated_probability_bps = int(yes_probability * BPS_CONSTANT)
65
+ # Dune dashboard expects the probs to be in the same order as on the market.
66
+ probabilities_converted = [
67
+ (outcome, int(probabilities.get(outcome, 0) * BPS_CONSTANT))
68
+ for outcome in outcomes
69
+ ]
54
70
 
55
71
  prediction = ContractPrediction(
72
+ market=market_id,
56
73
  publisher=keys.bet_from_address,
57
74
  ipfs_hash=ipfs_hash_decoded,
58
75
  tx_hashes=tx_hashes,
59
- estimated_probability_bps=estimated_probability_bps,
76
+ outcomes=[x[0] for x in probabilities_converted],
77
+ estimated_probabilities_bps=[x[1] for x in probabilities_converted],
60
78
  )
61
- tx_receipt = OmenAgentResultMappingContract().add_prediction(
79
+ tx_receipt = contract.add_prediction(
62
80
  api_keys=keys,
63
- market_address=Web3.to_checksum_address(market_id),
81
+ market_address=market_id,
64
82
  prediction=prediction,
65
83
  web3=web3,
66
84
  )
67
85
  logger.info(
68
- f"Added prediction to market {market_id}. - receipt {tx_receipt['transactionHash'].hex()}."
86
+ f"Added prediction to market {market_id}. - receipt {tx_receipt['transactionHash'].to_0x_hex()}."
69
87
  )