prediction-market-agent-tooling 0.64.12.dev660__py3-none-any.whl → 0.65.1__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 (53) hide show
  1. prediction_market_agent_tooling/benchmark/agents.py +19 -16
  2. prediction_market_agent_tooling/benchmark/benchmark.py +94 -84
  3. prediction_market_agent_tooling/benchmark/utils.py +8 -9
  4. prediction_market_agent_tooling/config.py +28 -7
  5. prediction_market_agent_tooling/deploy/agent.py +85 -125
  6. prediction_market_agent_tooling/deploy/agent_example.py +20 -10
  7. prediction_market_agent_tooling/deploy/betting_strategy.py +222 -96
  8. prediction_market_agent_tooling/deploy/constants.py +4 -0
  9. prediction_market_agent_tooling/jobs/jobs_models.py +15 -4
  10. prediction_market_agent_tooling/jobs/omen/omen_jobs.py +3 -3
  11. prediction_market_agent_tooling/loggers.py +1 -0
  12. prediction_market_agent_tooling/markets/agent_market.py +145 -50
  13. prediction_market_agent_tooling/markets/blockchain_utils.py +10 -1
  14. prediction_market_agent_tooling/markets/data_models.py +83 -17
  15. prediction_market_agent_tooling/markets/manifold/api.py +18 -7
  16. prediction_market_agent_tooling/markets/manifold/data_models.py +23 -16
  17. prediction_market_agent_tooling/markets/manifold/manifold.py +18 -18
  18. prediction_market_agent_tooling/markets/manifold/utils.py +7 -12
  19. prediction_market_agent_tooling/markets/markets.py +2 -1
  20. prediction_market_agent_tooling/markets/metaculus/metaculus.py +29 -4
  21. prediction_market_agent_tooling/markets/omen/data_models.py +17 -32
  22. prediction_market_agent_tooling/markets/omen/omen.py +65 -108
  23. prediction_market_agent_tooling/markets/omen/omen_contracts.py +2 -5
  24. prediction_market_agent_tooling/markets/omen/omen_resolving.py +13 -13
  25. prediction_market_agent_tooling/markets/omen/omen_subgraph_handler.py +18 -12
  26. prediction_market_agent_tooling/markets/polymarket/data_models.py +7 -3
  27. prediction_market_agent_tooling/markets/polymarket/data_models_web.py +7 -3
  28. prediction_market_agent_tooling/markets/polymarket/polymarket.py +5 -4
  29. prediction_market_agent_tooling/markets/seer/data_models.py +0 -83
  30. prediction_market_agent_tooling/markets/seer/price_manager.py +44 -30
  31. prediction_market_agent_tooling/markets/seer/seer.py +105 -105
  32. prediction_market_agent_tooling/markets/seer/seer_subgraph_handler.py +34 -41
  33. prediction_market_agent_tooling/tools/betting_strategies/kelly_criterion.py +1 -1
  34. prediction_market_agent_tooling/tools/cow/cow_order.py +10 -3
  35. prediction_market_agent_tooling/tools/is_predictable.py +2 -3
  36. prediction_market_agent_tooling/tools/langfuse_client_utils.py +4 -4
  37. prediction_market_agent_tooling/tools/omen/sell_positions.py +3 -2
  38. prediction_market_agent_tooling/tools/utils.py +26 -13
  39. {prediction_market_agent_tooling-0.64.12.dev660.dist-info → prediction_market_agent_tooling-0.65.1.dist-info}/METADATA +2 -2
  40. {prediction_market_agent_tooling-0.64.12.dev660.dist-info → prediction_market_agent_tooling-0.65.1.dist-info}/RECORD +43 -53
  41. prediction_market_agent_tooling/monitor/financial_metrics/financial_metrics.py +0 -68
  42. prediction_market_agent_tooling/monitor/markets/manifold.py +0 -90
  43. prediction_market_agent_tooling/monitor/markets/metaculus.py +0 -43
  44. prediction_market_agent_tooling/monitor/markets/omen.py +0 -88
  45. prediction_market_agent_tooling/monitor/markets/polymarket.py +0 -49
  46. prediction_market_agent_tooling/monitor/monitor.py +0 -406
  47. prediction_market_agent_tooling/monitor/monitor_app.py +0 -149
  48. prediction_market_agent_tooling/monitor/monitor_settings.py +0 -27
  49. prediction_market_agent_tooling/tools/betting_strategies/market_moving.py +0 -146
  50. prediction_market_agent_tooling/tools/betting_strategies/minimum_bet_to_win.py +0 -12
  51. {prediction_market_agent_tooling-0.64.12.dev660.dist-info → prediction_market_agent_tooling-0.65.1.dist-info}/LICENSE +0 -0
  52. {prediction_market_agent_tooling-0.64.12.dev660.dist-info → prediction_market_agent_tooling-0.65.1.dist-info}/WHEEL +0 -0
  53. {prediction_market_agent_tooling-0.64.12.dev660.dist-info → prediction_market_agent_tooling-0.65.1.dist-info}/entry_points.txt +0 -0
@@ -1,11 +1,10 @@
1
1
  import typing as t
2
- from math import ceil
3
2
 
4
3
  from prediction_market_agent_tooling.config import APIKeys
5
4
  from prediction_market_agent_tooling.gtypes import (
6
5
  USD,
7
6
  CollateralToken,
8
- Mana,
7
+ OutcomeStr,
9
8
  Probability,
10
9
  )
11
10
  from prediction_market_agent_tooling.markets.agent_market import (
@@ -25,9 +24,6 @@ from prediction_market_agent_tooling.markets.manifold.data_models import (
25
24
  FullManifoldMarket,
26
25
  usd_to_mana,
27
26
  )
28
- from prediction_market_agent_tooling.tools.betting_strategies.minimum_bet_to_win import (
29
- minimum_bet_to_win,
30
- )
31
27
  from prediction_market_agent_tooling.tools.utils import DatetimeUTC
32
28
 
33
29
 
@@ -45,22 +41,17 @@ class ManifoldAgentMarket(AgentMarket):
45
41
  absolute=0.25, # For doing trades via API.
46
42
  )
47
43
 
44
+ # We restrict Manifold to binary markets, hence current_p_yes always defined.
45
+ current_p_yes: Probability
46
+
48
47
  def get_last_trade_p_yes(self) -> Probability:
49
48
  """On Manifold, probablities aren't updated after the closure, so we can just use the current probability"""
50
49
  return self.current_p_yes
51
50
 
52
- def get_last_trade_p_no(self) -> Probability:
53
- """On Manifold, probablities aren't updated after the closure, so we can just use the current probability"""
54
- return self.current_p_no
55
-
56
51
  def get_tiny_bet_amount(self) -> CollateralToken:
57
52
  return CollateralToken(1)
58
53
 
59
- def get_minimum_bet_to_win(self, answer: bool, amount_to_win: float) -> Mana:
60
- # Manifold lowest bet is 1 Mana, so we need to ceil the result.
61
- return Mana(ceil(minimum_bet_to_win(answer, amount_to_win, self)))
62
-
63
- def place_bet(self, outcome: bool, amount: USD) -> str:
54
+ def place_bet(self, outcome: OutcomeStr, amount: USD) -> str:
64
55
  self.get_usd_in_token(amount)
65
56
  bet = place_bet(
66
57
  amount=usd_to_mana(amount),
@@ -72,6 +63,15 @@ class ManifoldAgentMarket(AgentMarket):
72
63
 
73
64
  @staticmethod
74
65
  def from_data_model(model: FullManifoldMarket) -> "ManifoldAgentMarket":
66
+ outcome_token_pool = {o: model.pool.size_for_outcome(o) for o in model.outcomes}
67
+
68
+ prob_map = AgentMarket.build_probability_map(
69
+ outcomes=list(outcome_token_pool.keys()),
70
+ outcome_token_amounts=list(
71
+ [i.as_outcome_wei for i in outcome_token_pool.values()]
72
+ ),
73
+ )
74
+
75
75
  return ManifoldAgentMarket(
76
76
  id=model.id,
77
77
  question=model.question,
@@ -83,18 +83,18 @@ class ManifoldAgentMarket(AgentMarket):
83
83
  current_p_yes=model.probability,
84
84
  url=model.url,
85
85
  volume=model.volume,
86
- outcome_token_pool={
87
- o: model.pool.size_for_outcome(o) for o in model.outcomes
88
- },
86
+ outcome_token_pool=outcome_token_pool,
87
+ probabilities=prob_map,
89
88
  )
90
89
 
91
90
  @staticmethod
92
- def get_binary_markets(
91
+ def get_markets(
93
92
  limit: int,
94
93
  sort_by: SortBy,
95
94
  filter_by: FilterBy = FilterBy.OPEN,
96
95
  created_after: t.Optional[DatetimeUTC] = None,
97
96
  excluded_questions: set[str] | None = None,
97
+ fetch_categorical_markets: bool = False,
98
98
  ) -> t.Sequence["ManifoldAgentMarket"]:
99
99
  sort: t.Literal["newest", "close-date"] | None
100
100
  if sort_by == SortBy.CLOSING_SOONEST:
@@ -1,15 +1,10 @@
1
+ import typing as t
2
+
3
+ from prediction_market_agent_tooling.gtypes import OutcomeStr
1
4
  from prediction_market_agent_tooling.markets.data_models import Resolution
2
- from prediction_market_agent_tooling.markets.manifold.api import (
3
- get_manifold_binary_markets,
4
- )
5
5
 
6
6
 
7
- def find_resolution_on_manifold(question: str, n: int = 100) -> Resolution | None:
8
- # Even with exact-match search, Manifold doesn't return it as the first result, increase `n` if you can't find market that you know exists.
9
- manifold_markets = get_manifold_binary_markets(
10
- n, term=question, filter_=None, sort=None
11
- )
12
- for manifold_market in manifold_markets:
13
- if manifold_market.question == question:
14
- return manifold_market.resolution
15
- return None
7
+ def validate_resolution(v: t.Any) -> Resolution:
8
+ if isinstance(v, str):
9
+ return Resolution(outcome=OutcomeStr(v), invalid=False)
10
+ raise ValueError(f"Expected a string, got {v} {type(v)}")
@@ -69,6 +69,7 @@ MARKET_TYPE_TO_AGENT_MARKET: dict[MarketType, type[AgentMarket]] = {
69
69
  MarketType.SEER: SeerAgentMarket,
70
70
  }
71
71
 
72
+
72
73
  JOB_MARKET_TYPE_TO_JOB_AGENT_MARKET: dict[MarketType, type[JobAgentMarket]] = {
73
74
  MarketType.OMEN: OmenJobAgentMarket,
74
75
  }
@@ -83,7 +84,7 @@ def get_binary_markets(
83
84
  created_after: DatetimeUTC | None = None,
84
85
  ) -> t.Sequence[AgentMarket]:
85
86
  agent_market_class = MARKET_TYPE_TO_AGENT_MARKET[market_type]
86
- markets = agent_market_class.get_binary_markets(
87
+ markets = agent_market_class.get_markets(
87
88
  limit=limit,
88
89
  sort_by=sort_by,
89
90
  filter_by=filter_by,
@@ -1,6 +1,11 @@
1
1
  import typing as t
2
2
 
3
+ from pydantic import field_validator
4
+ from pydantic_core.core_schema import FieldValidationInfo
5
+
3
6
  from prediction_market_agent_tooling.config import APIKeys
7
+ from prediction_market_agent_tooling.gtypes import OutcomeStr, Probability
8
+ from prediction_market_agent_tooling.loggers import logger
4
9
  from prediction_market_agent_tooling.markets.agent_market import (
5
10
  AgentMarket,
6
11
  FilterBy,
@@ -32,14 +37,29 @@ class MetaculusAgentMarket(AgentMarket):
32
37
  resolution_criteria: str
33
38
  fees: MarketFees = MarketFees.get_zero_fees() # No fees on Metaculus.
34
39
 
40
+ @field_validator("probabilities")
41
+ def validate_probabilities(
42
+ cls,
43
+ probs: dict[OutcomeStr, Probability],
44
+ info: FieldValidationInfo,
45
+ ) -> dict[OutcomeStr, Probability]:
46
+ outcomes: t.Sequence[OutcomeStr] = check_not_none(info.data.get("outcomes"))
47
+ # We don't check for outcomes match because Metaculus has no filled outcomes.
48
+ total = float(sum(probs.values()))
49
+ if not 0.999 <= total <= 1.001:
50
+ # We simply log a warning because for some use-cases (e.g. existing positions), the
51
+ # markets might be already closed hence no reliable outcome token prices exist anymore.
52
+ logger.warning(f"Probabilities for market {info.data=} do not sum to 1.")
53
+ return probs
54
+
35
55
  @staticmethod
36
56
  def from_data_model(model: MetaculusQuestion) -> "MetaculusAgentMarket":
57
+ probabilities = AgentMarket.build_probability_map_from_p_yes(p_yes=model.p_yes)
37
58
  return MetaculusAgentMarket(
38
59
  id=str(model.id),
39
60
  question=model.title,
40
61
  outcomes=[],
41
62
  resolution=None,
42
- current_p_yes=model.p_yes,
43
63
  created_time=model.created_at,
44
64
  close_time=model.scheduled_close_time,
45
65
  url=model.page_url,
@@ -49,10 +69,11 @@ class MetaculusAgentMarket(AgentMarket):
49
69
  description=model.question.description,
50
70
  fine_print=model.question.fine_print,
51
71
  resolution_criteria=model.question.resolution_criteria,
72
+ probabilities=probabilities,
52
73
  )
53
74
 
54
75
  @staticmethod
55
- def get_binary_markets(
76
+ def get_markets(
56
77
  limit: int,
57
78
  sort_by: SortBy = SortBy.NONE,
58
79
  filter_by: FilterBy = FilterBy.OPEN,
@@ -109,8 +130,12 @@ class MetaculusAgentMarket(AgentMarket):
109
130
  def store_prediction(
110
131
  self, processed_market: ProcessedMarket | None, keys: APIKeys, agent_name: str
111
132
  ) -> None:
112
- if processed_market is not None:
113
- make_prediction(self.id, processed_market.answer.p_yes)
133
+ if (
134
+ processed_market is not None
135
+ and processed_market.answer.get_yes_probability() is not None
136
+ ):
137
+ yes_prob = check_not_none(processed_market.answer.get_yes_probability())
138
+ make_prediction(self.id, yes_prob)
114
139
  post_question_comment(
115
140
  self.id,
116
141
  check_not_none(
@@ -63,7 +63,7 @@ def get_boolean_outcome(outcome_str: str) -> bool:
63
63
  raise ValueError(f"Outcome `{outcome_str}` is not a valid boolean outcome.")
64
64
 
65
65
 
66
- def get_bet_outcome(binary_outcome: bool) -> str:
66
+ def get_bet_outcome(binary_outcome: bool) -> OutcomeStr:
67
67
  return OMEN_TRUE_OUTCOME if binary_outcome else OMEN_FALSE_OUTCOME
68
68
 
69
69
 
@@ -399,31 +399,16 @@ class OmenMarket(BaseModel):
399
399
  def is_binary(self) -> bool:
400
400
  return len(self.outcomes) == 2
401
401
 
402
- def boolean_outcome_from_answer(self, answer: HexBytes) -> bool:
403
- if not self.is_binary:
404
- raise ValueError(
405
- f"Market with title {self.title} is not binary, it has {len(self.outcomes)} outcomes."
406
- )
407
- outcome: str = self.outcomes[answer.as_int()]
408
- return get_boolean_outcome(outcome)
409
-
410
- @property
411
- def boolean_outcome(self) -> bool:
412
- if not self.is_resolved_with_valid_answer:
413
- raise ValueError(f"Bet with title {self.title} is not resolved.")
414
- return self.boolean_outcome_from_answer(
415
- check_not_none(
416
- self.currentAnswer, "Can not be None if `is_resolved_with_valid_answer`"
417
- )
418
- )
402
+ def outcome_from_answer(self, answer: HexBytes) -> OutcomeStr | None:
403
+ if answer == INVALID_ANSWER_HEX_BYTES:
404
+ return None
405
+ return self.outcomes[answer.as_int()]
419
406
 
420
407
  def get_resolution_enum_from_answer(self, answer: HexBytes) -> Resolution:
421
- if answer == INVALID_ANSWER_HEX_BYTES:
422
- return Resolution.CANCEL
423
- elif self.boolean_outcome_from_answer(answer):
424
- return Resolution.YES
425
- else:
426
- return Resolution.NO
408
+ if outcome := self.outcome_from_answer(answer):
409
+ return Resolution.from_answer(outcome)
410
+
411
+ return Resolution(outcome=None, invalid=True)
427
412
 
428
413
  def get_resolution_enum(self) -> t.Optional[Resolution]:
429
414
  if not self.is_resolved_with_valid_answer:
@@ -561,10 +546,6 @@ class OmenBet(BaseModel):
561
546
  def creation_datetime(self) -> DatetimeUTC:
562
547
  return DatetimeUTC.to_datetime_utc(self.creationTimestamp)
563
548
 
564
- @property
565
- def boolean_outcome(self) -> bool:
566
- return get_boolean_outcome(self.fpmm.outcomes[self.outcomeIndex])
567
-
568
549
  @property
569
550
  def old_probability(self) -> Probability:
570
551
  # Old marginal price is the probability of the outcome before placing this bet.
@@ -582,9 +563,13 @@ class OmenBet(BaseModel):
582
563
 
583
564
  def get_profit(self) -> CollateralToken:
584
565
  bet_amount = self.collateral_amount_token
566
+
567
+ if not self.fpmm.has_valid_answer:
568
+ return CollateralToken(0)
569
+
585
570
  profit = (
586
571
  self.outcomeTokensTraded.as_outcome_token.as_token - bet_amount
587
- if self.boolean_outcome == self.fpmm.boolean_outcome
572
+ if self.outcomeIndex == self.fpmm.answer_index
588
573
  else -bet_amount
589
574
  )
590
575
  return profit
@@ -594,7 +579,7 @@ class OmenBet(BaseModel):
594
579
  id=str(self.transactionHash),
595
580
  # Use the transaction hash instead of the bet id - both are valid, but we return the transaction hash from the trade functions, so be consistent here.
596
581
  amount=self.collateral_amount_token,
597
- outcome=self.boolean_outcome,
582
+ outcome=self.fpmm.outcomes[self.outcomeIndex],
598
583
  created_time=self.creation_datetime,
599
584
  market_question=self.title,
600
585
  market_id=self.fpmm.id,
@@ -610,11 +595,11 @@ class OmenBet(BaseModel):
610
595
  id=self.transactionHash.hex(),
611
596
  # Use the transaction hash instead of the bet id - both are valid, but we return the transaction hash from the trade functions, so be consistent here.
612
597
  amount=self.collateral_amount_token,
613
- outcome=self.boolean_outcome,
598
+ outcome=self.fpmm.outcomes[self.outcomeIndex],
614
599
  created_time=self.creation_datetime,
615
600
  market_question=self.title,
616
601
  market_id=self.fpmm.id,
617
- market_outcome=self.fpmm.boolean_outcome,
602
+ market_outcome=self.fpmm.outcomes[self.outcomeIndex],
618
603
  resolved_time=check_not_none(self.fpmm.finalized_datetime),
619
604
  profit=self.get_profit(),
620
605
  )