prediction-market-agent-tooling 0.68.1__py3-none-any.whl → 0.69.0__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 (19) hide show
  1. prediction_market_agent_tooling/deploy/agent.py +13 -2
  2. prediction_market_agent_tooling/deploy/betting_strategy.py +132 -29
  3. prediction_market_agent_tooling/jobs/omen/omen_jobs.py +14 -17
  4. prediction_market_agent_tooling/markets/agent_market.py +11 -0
  5. prediction_market_agent_tooling/markets/markets.py +16 -0
  6. prediction_market_agent_tooling/markets/seer/data_models.py +40 -5
  7. prediction_market_agent_tooling/markets/seer/seer.py +44 -8
  8. prediction_market_agent_tooling/markets/seer/seer_api.py +28 -0
  9. prediction_market_agent_tooling/markets/seer/seer_subgraph_handler.py +66 -19
  10. prediction_market_agent_tooling/markets/seer/subgraph_data_models.py +65 -0
  11. prediction_market_agent_tooling/tools/betting_strategies/kelly_criterion.py +17 -22
  12. prediction_market_agent_tooling/tools/datetime_utc.py +14 -2
  13. prediction_market_agent_tooling/tools/langfuse_client_utils.py +17 -5
  14. prediction_market_agent_tooling/tools/tokens/auto_deposit.py +1 -1
  15. {prediction_market_agent_tooling-0.68.1.dist-info → prediction_market_agent_tooling-0.69.0.dist-info}/METADATA +1 -1
  16. {prediction_market_agent_tooling-0.68.1.dist-info → prediction_market_agent_tooling-0.69.0.dist-info}/RECORD +19 -18
  17. {prediction_market_agent_tooling-0.68.1.dist-info → prediction_market_agent_tooling-0.69.0.dist-info}/LICENSE +0 -0
  18. {prediction_market_agent_tooling-0.68.1.dist-info → prediction_market_agent_tooling-0.69.0.dist-info}/WHEEL +0 -0
  19. {prediction_market_agent_tooling-0.68.1.dist-info → prediction_market_agent_tooling-0.69.0.dist-info}/entry_points.txt +0 -0
@@ -630,7 +630,7 @@ class DeployableTraderAgent(DeployablePredictionAgent):
630
630
  api_keys = APIKeys()
631
631
 
632
632
  # Get the strategy to know how much it will bet.
633
- strategy = self.get_betting_strategy(market)
633
+ strategy = self.get_betting_strategy_supported(market)
634
634
  # Have a little bandwidth after the bet.
635
635
  min_required_balance_to_trade = strategy.maximum_possible_bet_amount * 1.01
636
636
 
@@ -658,13 +658,24 @@ class DeployableTraderAgent(DeployablePredictionAgent):
658
658
  total_amount = self.get_total_amount_to_bet(market)
659
659
  return CategoricalMaxAccuracyBettingStrategy(max_position_amount=total_amount)
660
660
 
661
+ def get_betting_strategy_supported(self, market: AgentMarket) -> BettingStrategy:
662
+ """
663
+ Use this class internally to assert that the configured betting strategy works with the given market.
664
+ """
665
+ strategy = self.get_betting_strategy(market=market)
666
+ if not strategy.is_market_supported(market):
667
+ raise ValueError(
668
+ f"Market {market.url} is not supported by the strategy {strategy}."
669
+ )
670
+ return strategy
671
+
661
672
  def build_trades(
662
673
  self,
663
674
  market: AgentMarket,
664
675
  answer: CategoricalProbabilisticAnswer,
665
676
  existing_position: ExistingPosition | None,
666
677
  ) -> list[Trade]:
667
- strategy = self.get_betting_strategy(market=market)
678
+ strategy = self.get_betting_strategy_supported(market=market)
668
679
  trades = strategy.calculate_trades(existing_position, answer, market)
669
680
  return trades
670
681
 
@@ -16,7 +16,11 @@ from prediction_market_agent_tooling.gtypes import (
16
16
  Probability,
17
17
  )
18
18
  from prediction_market_agent_tooling.loggers import logger
19
- from prediction_market_agent_tooling.markets.agent_market import AgentMarket, MarketFees
19
+ from prediction_market_agent_tooling.markets.agent_market import (
20
+ AgentMarket,
21
+ MarketFees,
22
+ QuestionType,
23
+ )
20
24
  from prediction_market_agent_tooling.markets.data_models import (
21
25
  CategoricalProbabilisticAnswer,
22
26
  ExistingPosition,
@@ -24,10 +28,12 @@ from prediction_market_agent_tooling.markets.data_models import (
24
28
  Trade,
25
29
  TradeType,
26
30
  )
31
+ from prediction_market_agent_tooling.markets.markets import MarketType
27
32
  from prediction_market_agent_tooling.markets.omen.omen import (
28
33
  get_buy_outcome_token_amount,
29
34
  )
30
35
  from prediction_market_agent_tooling.tools.betting_strategies.kelly_criterion import (
36
+ KellyType,
31
37
  get_kelly_bet_full,
32
38
  get_kelly_bet_simplified,
33
39
  get_kelly_bets_categorical_full,
@@ -45,9 +51,21 @@ class GuaranteedLossError(RuntimeError):
45
51
 
46
52
 
47
53
  class BettingStrategy(ABC):
54
+ supported_question_types: set[QuestionType]
55
+ supported_market_types: set[MarketType]
56
+
48
57
  def __init__(self, take_profit: bool = True) -> None:
49
58
  self.take_profit = take_profit
50
59
 
60
+ def is_market_supported(self, market: AgentMarket) -> bool:
61
+ if market.question_type not in self.supported_question_types:
62
+ return False
63
+
64
+ if MarketType.from_market(market) not in self.supported_market_types:
65
+ return False
66
+
67
+ return True
68
+
51
69
  @abstractmethod
52
70
  def calculate_trades(
53
71
  self,
@@ -251,6 +269,13 @@ class BettingStrategy(ABC):
251
269
 
252
270
 
253
271
  class CategoricalMaxAccuracyBettingStrategy(BettingStrategy):
272
+ supported_question_types = {
273
+ QuestionType.BINARY,
274
+ QuestionType.CATEGORICAL,
275
+ QuestionType.SCALAR,
276
+ }
277
+ supported_market_types = {x for x in MarketType if x.is_trading_market}
278
+
254
279
  def __init__(self, max_position_amount: USD, take_profit: bool = True):
255
280
  super().__init__(take_profit=take_profit)
256
281
  self.max_position_amount = max_position_amount
@@ -362,18 +387,20 @@ class MaxExpectedValueBettingStrategy(CategoricalMaxAccuracyBettingStrategy):
362
387
  return f"MaxExpectedValueBettingStrategy(max_position_amount={self.max_position_amount}, take_profit={self.take_profit})"
363
388
 
364
389
 
365
- class BinaryKellyBettingStrategy(BettingStrategy):
390
+ class _BinaryKellyBettingStrategy(BettingStrategy):
391
+ supported_question_types = {QuestionType.BINARY}
392
+
366
393
  def __init__(
367
394
  self,
395
+ kelly_type: KellyType,
368
396
  max_position_amount: USD,
369
397
  max_price_impact: float | None = None,
370
398
  take_profit: bool = True,
371
- force_simplified_calculation: bool = False,
372
399
  ):
373
400
  super().__init__(take_profit=take_profit)
401
+ self.kelly_type = kelly_type
374
402
  self.max_position_amount = max_position_amount
375
403
  self.max_price_impact = max_price_impact
376
- self.force_simplified_calculation = force_simplified_calculation
377
404
 
378
405
  @property
379
406
  def maximum_possible_bet_amount(self) -> USD:
@@ -396,7 +423,7 @@ class BinaryKellyBettingStrategy(BettingStrategy):
396
423
  else override_p_yes
397
424
  )
398
425
 
399
- if market.outcome_token_pool is None or self.force_simplified_calculation:
426
+ if self.kelly_type == KellyType.SIMPLE:
400
427
  kelly_bet = get_kelly_bet_simplified(
401
428
  max_bet=market.get_usd_in_token(self.max_position_amount),
402
429
  market_p_yes=market.probability_for_market_outcome(direction),
@@ -447,7 +474,6 @@ class BinaryKellyBettingStrategy(BettingStrategy):
447
474
  # Adjust amount
448
475
  max_price_impact_bet_amount = self.calculate_bet_amount_for_price_impact(
449
476
  market,
450
- kelly_bet.size,
451
477
  direction=direction,
452
478
  max_price_impact=self.max_price_impact,
453
479
  )
@@ -492,7 +518,6 @@ class BinaryKellyBettingStrategy(BettingStrategy):
492
518
  @staticmethod
493
519
  def calculate_bet_amount_for_price_impact(
494
520
  market: AgentMarket,
495
- kelly_bet_size: CollateralToken,
496
521
  direction: OutcomeStr,
497
522
  max_price_impact: float,
498
523
  ) -> CollateralToken:
@@ -501,7 +526,7 @@ class BinaryKellyBettingStrategy(BettingStrategy):
501
526
  ) -> float:
502
527
  outcome_idx = market.get_outcome_index(direction)
503
528
  price_impact = (
504
- BinaryKellyBettingStrategy.calculate_price_impact_for_bet_amount(
529
+ _BinaryKellyBettingStrategy.calculate_price_impact_for_bet_amount(
505
530
  outcome_idx=outcome_idx,
506
531
  bet_amount=CollateralToken(bet_amount_collateral),
507
532
  pool_balances=pool_balances,
@@ -512,20 +537,13 @@ class BinaryKellyBettingStrategy(BettingStrategy):
512
537
  return abs(price_impact - max_price_impact)
513
538
 
514
539
  if not market.outcome_token_pool:
515
- logger.warning(
540
+ raise ValueError(
516
541
  "Market outcome_token_pool is None, cannot calculate bet amount"
517
542
  )
518
- return kelly_bet_size
519
-
520
- filtered_pool = {
521
- outcome: pool_value
522
- for outcome, pool_value in market.outcome_token_pool.items()
523
- if INVALID_OUTCOME_LOWERCASE_IDENTIFIER not in outcome.lower()
524
- }
525
543
 
526
- pool_balances = [i.as_outcome_wei for i in filtered_pool.values()]
544
+ pool_balances = [i.as_outcome_wei for i in market.outcome_token_pool.values()]
527
545
  # stay float for compatibility with `minimize_scalar`
528
- total_pool_balance = sum([i.value for i in filtered_pool.values()])
546
+ total_pool_balance = sum([i.value for i in market.outcome_token_pool.values()])
529
547
 
530
548
  # The bounds below have been found to work heuristically.
531
549
  optimized_bet_amount = minimize_scalar(
@@ -538,10 +556,47 @@ class BinaryKellyBettingStrategy(BettingStrategy):
538
556
  return CollateralToken(optimized_bet_amount.x)
539
557
 
540
558
  def __repr__(self) -> str:
541
- return f"{self.__class__.__name__}(max_position_amount={self.max_position_amount}, max_price_impact={self.max_price_impact}, take_profit={self.take_profit}, force_simplified_calculation={self.force_simplified_calculation})"
559
+ return f"{self.__class__.__name__}(max_position_amount={self.max_position_amount}, max_price_impact={self.max_price_impact}, take_profit={self.take_profit})"
560
+
561
+
562
+ class SimpleBinaryKellyBettingStrategy(_BinaryKellyBettingStrategy):
563
+ supported_market_types = {x for x in MarketType if x.is_trading_market}
564
+
565
+ def __init__(
566
+ self,
567
+ max_position_amount: USD,
568
+ take_profit: bool = True,
569
+ ):
570
+ super().__init__(
571
+ kelly_type=KellyType.SIMPLE,
572
+ max_position_amount=max_position_amount,
573
+ max_price_impact=None,
574
+ take_profit=take_profit,
575
+ )
576
+
577
+
578
+ class FullBinaryKellyBettingStrategy(_BinaryKellyBettingStrategy):
579
+ # Supports only OMEN because it uses closed-form formula derived from a binary FPMM.
580
+ supported_market_types = {MarketType.OMEN}
581
+
582
+ def __init__(
583
+ self,
584
+ max_position_amount: USD,
585
+ max_price_impact: float | None = None,
586
+ take_profit: bool = True,
587
+ ):
588
+ super().__init__(
589
+ kelly_type=KellyType.FULL,
590
+ max_position_amount=max_position_amount,
591
+ max_price_impact=max_price_impact,
592
+ take_profit=take_profit,
593
+ )
542
594
 
543
595
 
544
596
  class MaxAccuracyWithKellyScaledBetsStrategy(BettingStrategy):
597
+ supported_question_types = {QuestionType.BINARY}
598
+ supported_market_types = {MarketType.OMEN}
599
+
545
600
  def __init__(
546
601
  self,
547
602
  max_position_amount: USD,
@@ -573,7 +628,7 @@ class MaxAccuracyWithKellyScaledBetsStrategy(BettingStrategy):
573
628
  # We ignore the direction nudge given by Kelly, hence we assume we have a perfect prediction.
574
629
  estimated_p_yes = 1.0
575
630
 
576
- kelly_bet = BinaryKellyBettingStrategy(
631
+ kelly_bet = FullBinaryKellyBettingStrategy(
577
632
  max_position_amount=self.max_position_amount
578
633
  ).get_kelly_bet(
579
634
  market=market,
@@ -601,24 +656,27 @@ class MaxAccuracyWithKellyScaledBetsStrategy(BettingStrategy):
601
656
  return f"{self.__class__.__name__}(max_position_amount={self.max_position_amount}, take_profit={self.take_profit})"
602
657
 
603
658
 
604
- class CategoricalKellyBettingStrategy(BettingStrategy):
659
+ class _CategoricalKellyBettingStrategy(BettingStrategy):
660
+ supported_question_types = {QuestionType.BINARY, QuestionType.CATEGORICAL}
661
+ supported_market_types = {x for x in MarketType if x.is_trading_market}
662
+
605
663
  def __init__(
606
664
  self,
665
+ kelly_type: KellyType,
607
666
  max_position_amount: USD,
608
667
  max_price_impact: float | None,
609
668
  allow_multiple_bets: bool,
610
669
  allow_shorting: bool,
611
670
  multicategorical: bool,
612
671
  take_profit: bool = True,
613
- force_simplified_calculation: bool = False,
614
672
  ):
615
673
  super().__init__(take_profit=take_profit)
674
+ self.kelly_type = kelly_type
616
675
  self.max_position_amount = max_position_amount
617
676
  self.max_price_impact = max_price_impact
618
677
  self.allow_multiple_bets = allow_multiple_bets
619
678
  self.allow_shorting = allow_shorting
620
679
  self.multicategorical = multicategorical
621
- self.force_simplified_calculation = force_simplified_calculation
622
680
 
623
681
  @property
624
682
  def maximum_possible_bet_amount(self) -> USD:
@@ -632,7 +690,7 @@ class CategoricalKellyBettingStrategy(BettingStrategy):
632
690
  ) -> list[CategoricalKellyBet]:
633
691
  max_bet = market.get_usd_in_token(max_bet_amount)
634
692
 
635
- if market.outcome_token_pool is None or self.force_simplified_calculation:
693
+ if self.kelly_type == KellyType.SIMPLE:
636
694
  kelly_bets = get_kelly_bets_categorical_simplified(
637
695
  market_probabilities=[market.probabilities[o] for o in market.outcomes],
638
696
  estimated_probabilities=[
@@ -647,8 +705,8 @@ class CategoricalKellyBettingStrategy(BettingStrategy):
647
705
 
648
706
  else:
649
707
  kelly_bets = get_kelly_bets_categorical_full(
650
- outcome_pool_sizes=[
651
- market.outcome_token_pool[o] for o in market.outcomes
708
+ market_probabilities=[
709
+ market.probability_for_market_outcome(o) for o in market.outcomes
652
710
  ],
653
711
  estimated_probabilities=[
654
712
  answer.probability_for_market_outcome(o) for o in market.outcomes
@@ -659,6 +717,11 @@ class CategoricalKellyBettingStrategy(BettingStrategy):
659
717
  allow_multiple_bets=self.allow_multiple_bets,
660
718
  allow_shorting=self.allow_shorting,
661
719
  multicategorical=self.multicategorical,
720
+ get_buy_token_amount=lambda bet_amount, outcome_index: check_not_none(
721
+ market.get_buy_token_amount(
722
+ bet_amount, market.get_outcome_str(outcome_index)
723
+ )
724
+ ),
662
725
  )
663
726
 
664
727
  return kelly_bets
@@ -689,9 +752,8 @@ class CategoricalKellyBettingStrategy(BettingStrategy):
689
752
  if self.max_price_impact:
690
753
  # Adjust amount
691
754
  max_price_impact_bet_amount = (
692
- BinaryKellyBettingStrategy.calculate_bet_amount_for_price_impact(
755
+ _BinaryKellyBettingStrategy.calculate_bet_amount_for_price_impact(
693
756
  market,
694
- best_kelly_bet.size,
695
757
  direction=market.get_outcome_str(best_kelly_bet.index),
696
758
  max_price_impact=self.max_price_impact,
697
759
  )
@@ -712,4 +774,45 @@ class CategoricalKellyBettingStrategy(BettingStrategy):
712
774
  return trades
713
775
 
714
776
  def __repr__(self) -> str:
715
- return f"{self.__class__.__name__}(max_position_amount={self.max_position_amount}, max_price_impact={self.max_price_impact}, allow_multiple_bets={self.allow_multiple_bets}, allow_shorting={self.allow_shorting}, take_profit={self.take_profit}, force_simplified_calculation={self.force_simplified_calculation})"
777
+ return f"{self.__class__.__name__}(max_position_amount={self.max_position_amount}, max_price_impact={self.max_price_impact}, allow_multiple_bets={self.allow_multiple_bets}, allow_shorting={self.allow_shorting}, take_profit={self.take_profit})"
778
+
779
+
780
+ class SimpleCategoricalKellyBettingStrategy(_CategoricalKellyBettingStrategy):
781
+ def __init__(
782
+ self,
783
+ max_position_amount: USD,
784
+ allow_multiple_bets: bool,
785
+ allow_shorting: bool,
786
+ multicategorical: bool,
787
+ take_profit: bool = True,
788
+ ):
789
+ super().__init__(
790
+ kelly_type=KellyType.SIMPLE,
791
+ max_position_amount=max_position_amount,
792
+ max_price_impact=None,
793
+ allow_multiple_bets=allow_multiple_bets,
794
+ allow_shorting=allow_shorting,
795
+ multicategorical=multicategorical,
796
+ take_profit=take_profit,
797
+ )
798
+
799
+
800
+ class FullCategoricalKellyBettingStrategy(_CategoricalKellyBettingStrategy):
801
+ def __init__(
802
+ self,
803
+ max_position_amount: USD,
804
+ max_price_impact: float | None,
805
+ allow_multiple_bets: bool,
806
+ allow_shorting: bool,
807
+ multicategorical: bool,
808
+ take_profit: bool = True,
809
+ ):
810
+ super().__init__(
811
+ kelly_type=KellyType.FULL,
812
+ max_position_amount=max_position_amount,
813
+ max_price_impact=max_price_impact,
814
+ allow_multiple_bets=allow_multiple_bets,
815
+ allow_shorting=allow_shorting,
816
+ multicategorical=multicategorical,
817
+ take_profit=take_profit,
818
+ )
@@ -1,10 +1,6 @@
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
- BinaryKellyBettingStrategy,
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
6
  from prediction_market_agent_tooling.markets.agent_market import ProcessedMarket
@@ -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 = BinaryKellyBettingStrategy(max_position_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":
@@ -188,6 +188,17 @@ class AgentMarket(BaseModel):
188
188
  f"Could not find probability for market outcome {market_outcome}"
189
189
  )
190
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
+
191
202
  @property
192
203
  def is_binary(self) -> bool:
193
204
  # 3 outcomes can also be binary if 3rd outcome is invalid (Seer)
@@ -31,6 +31,10 @@ class MarketType(str, Enum):
31
31
  METACULUS = "metaculus"
32
32
  SEER = "seer"
33
33
 
34
+ @staticmethod
35
+ def from_market(market: AgentMarket) -> "MarketType":
36
+ return AGENT_MARKET_TO_MARKET_TYPE[type(market)]
37
+
34
38
  @property
35
39
  def market_class(self) -> type[AgentMarket]:
36
40
  if self not in MARKET_TYPE_TO_AGENT_MARKET:
@@ -43,6 +47,15 @@ class MarketType(str, Enum):
43
47
  raise ValueError(f"Unknown market type: {self}")
44
48
  return JOB_MARKET_TYPE_TO_JOB_AGENT_MARKET[self]
45
49
 
50
+ @property
51
+ def is_trading_market(self) -> bool:
52
+ return self in [
53
+ MarketType.OMEN,
54
+ MarketType.POLYMARKET,
55
+ MarketType.SEER,
56
+ MarketType.MANIFOLD,
57
+ ]
58
+
46
59
  @property
47
60
  def is_blockchain_market(self) -> bool:
48
61
  return self in [MarketType.OMEN, MarketType.POLYMARKET, MarketType.SEER]
@@ -56,6 +69,9 @@ MARKET_TYPE_TO_AGENT_MARKET: dict[MarketType, type[AgentMarket]] = {
56
69
  MarketType.SEER: SeerAgentMarket,
57
70
  }
58
71
 
72
+ AGENT_MARKET_TO_MARKET_TYPE: dict[type[AgentMarket], MarketType] = {
73
+ v: k for k, v in MARKET_TYPE_TO_AGENT_MARKET.items()
74
+ }
59
75
 
60
76
  JOB_MARKET_TYPE_TO_JOB_AGENT_MARKET: dict[MarketType, type[JobAgentMarket]] = {
61
77
  MarketType.OMEN: OmenJobAgentMarket,
@@ -1,5 +1,6 @@
1
1
  import typing as t
2
2
  from datetime import timedelta
3
+ from enum import Enum
3
4
  from typing import Annotated
4
5
  from urllib.parse import urljoin
5
6
 
@@ -139,11 +140,6 @@ class SeerMarket(BaseModel):
139
140
  for token in self.wrapped_tokens
140
141
  ]
141
142
 
142
- @property
143
- def is_binary(self) -> bool:
144
- # 3 because Seer has also third, `Invalid` outcome.
145
- return len(self.outcomes) == 3
146
-
147
143
  @property
148
144
  def collateral_token_contract_address_checksummed(self) -> ChecksumAddress:
149
145
  return Web3.to_checksum_address(self.collateral_token)
@@ -188,3 +184,42 @@ class ExactInputSingleParams(BaseModel):
188
184
  limit_sqrt_price: Wei = Field(
189
185
  alias="limitSqrtPrice", default_factory=lambda: Wei(0)
190
186
  ) # 0 for convenience, we also don't expect major price shifts
187
+
188
+
189
+ class SeerTransactionType(str, Enum):
190
+ SWAP = "swap"
191
+ SPLIT = "split"
192
+
193
+
194
+ class SeerTransaction(BaseModel):
195
+ model_config = ConfigDict(populate_by_name=True)
196
+
197
+ market_name: str = Field(alias="marketName")
198
+ market_id: HexBytes = Field(alias="marketId")
199
+ type: SeerTransactionType
200
+ block_number: int = Field(alias="blockNumber")
201
+ transaction_hash: HexBytes = Field(alias="transactionHash")
202
+ collateral: HexAddress
203
+ collateral_symbol: str = Field(alias="collateralSymbol")
204
+
205
+ token_in: HexAddress = Field(alias="tokenIn")
206
+ token_out: HexAddress = Field(alias="tokenOut")
207
+ amount_in: Wei = Field(alias="amountIn")
208
+ amount_out: Wei = Field(alias="amountOut")
209
+ token_in_symbol: str = Field(alias="tokenInSymbol")
210
+ token_out_symbol: str = Field(alias="tokenOutSymbol")
211
+ timestamp: int | None = None
212
+
213
+ amount: Wei | None = None
214
+
215
+ @property
216
+ def timestamp_dt(self) -> DatetimeUTC | None:
217
+ return DatetimeUTC.to_datetime_utc(self.timestamp) if self.timestamp else None
218
+
219
+ @property
220
+ def token_in_checksum(self) -> ChecksumAddress:
221
+ return Web3.to_checksum_address(self.token_in)
222
+
223
+ @property
224
+ def token_out_checksum(self) -> ChecksumAddress:
225
+ return Web3.to_checksum_address(self.token_out)
@@ -35,6 +35,7 @@ from prediction_market_agent_tooling.markets.blockchain_utils import store_trade
35
35
  from prediction_market_agent_tooling.markets.data_models import (
36
36
  ExistingPosition,
37
37
  Resolution,
38
+ ResolvedBet,
38
39
  )
39
40
  from prediction_market_agent_tooling.markets.market_fees import MarketFees
40
41
  from prediction_market_agent_tooling.markets.omen.omen import (
@@ -56,6 +57,7 @@ from prediction_market_agent_tooling.markets.seer.exceptions import (
56
57
  PriceCalculationError,
57
58
  )
58
59
  from prediction_market_agent_tooling.markets.seer.price_manager import PriceManager
60
+ from prediction_market_agent_tooling.markets.seer.seer_api import get_seer_transactions
59
61
  from prediction_market_agent_tooling.markets.seer.seer_contracts import (
60
62
  GnosisRouter,
61
63
  SeerMarketFactory,
@@ -81,7 +83,6 @@ from prediction_market_agent_tooling.tools.cow.cow_order import (
81
83
  OrderStatusError,
82
84
  get_orders_by_owner,
83
85
  get_trades_by_order_uid,
84
- get_trades_by_owner,
85
86
  swap_tokens_waiting,
86
87
  wait_for_order_completion,
87
88
  )
@@ -272,10 +273,12 @@ class SeerAgentMarket(AgentMarket):
272
273
  """
273
274
  We filter the markets using previous trades by the user so that we don't have to process all Seer markets.
274
275
  """
275
- trades_by_user = get_trades_by_owner(api_keys.bet_from_address)
276
+ trades_by_user = get_seer_transactions(
277
+ api_keys.bet_from_address, RPCConfig().CHAIN_ID
278
+ )
276
279
 
277
- traded_tokens = {t.buyToken for t in trades_by_user}.union(
278
- [t.sellToken for t in trades_by_user]
280
+ traded_tokens = {t.token_in_checksum for t in trades_by_user}.union(
281
+ [t.token_out_checksum for t in trades_by_user]
279
282
  )
280
283
  filtered_markets: list[SeerMarket] = []
281
284
  for market in markets:
@@ -313,19 +316,43 @@ class SeerAgentMarket(AgentMarket):
313
316
  for market in filtered_markets
314
317
  if market.is_redeemable(owner=api_keys.bet_from_address, web3=web3)
315
318
  ]
319
+ logger.info(f"Got {len(markets_to_redeem)} markets to redeem on Seer.")
316
320
 
317
321
  gnosis_router = GnosisRouter()
318
322
  for market in markets_to_redeem:
319
323
  try:
324
+ # GnosisRouter needs approval to use our outcome tokens
325
+ for i, token in enumerate(market.wrapped_tokens):
326
+ ContractERC20OnGnosisChain(
327
+ address=Web3.to_checksum_address(token)
328
+ ).approve(
329
+ api_keys,
330
+ for_address=gnosis_router.address,
331
+ amount_wei=market_balances[market.id][i].as_wei,
332
+ web3=web3,
333
+ )
334
+
335
+ # We can only ask for redeem of outcome tokens on correct outcomes
336
+ # TODO: Implement more complex use-cases: https://github.com/gnosis/prediction-market-agent-tooling/issues/850
337
+ amounts_to_redeem = [
338
+ (amount if numerator > 0 else OutcomeWei(0))
339
+ for amount, numerator in zip(
340
+ market_balances[market.id], market.payout_numerators
341
+ )
342
+ ]
343
+
344
+ # Redeem!
320
345
  params = RedeemParams(
321
346
  market=Web3.to_checksum_address(market.id),
322
347
  outcome_indices=list(range(len(market.payout_numerators))),
323
- amounts=market_balances[market.id],
348
+ amounts=amounts_to_redeem,
324
349
  )
325
350
  gnosis_router.redeem_to_base(api_keys, params=params, web3=web3)
326
- logger.info(f"Redeemed market {market.id.to_0x_hex()}")
327
- except Exception as e:
328
- logger.error(f"Failed to redeem market {market.id.to_0x_hex()}, {e}")
351
+ logger.info(f"Redeemed market {market.url}.")
352
+ except Exception:
353
+ logger.exception(
354
+ f"Failed to redeem market {market.url}, {market.outcomes}, with amounts {market_balances[market.id]} and payout numerators {market.payout_numerators}, and wrapped tokens {market.wrapped_tokens}."
355
+ )
329
356
 
330
357
  # GnosisRouter withdraws sDai into wxDAI/xDai on its own, so no auto-withdraw needed by us.
331
358
 
@@ -464,6 +491,15 @@ class SeerAgentMarket(AgentMarket):
464
491
 
465
492
  return market
466
493
 
494
+ @staticmethod
495
+ def get_resolved_bets_made_since(
496
+ better_address: ChecksumAddress,
497
+ start_time: DatetimeUTC,
498
+ end_time: DatetimeUTC | None,
499
+ ) -> list[ResolvedBet]:
500
+ # TODO: https://github.com/gnosis/prediction-market-agent-tooling/issues/841
501
+ raise NotImplementedError()
502
+
467
503
  @staticmethod
468
504
  def get_markets(
469
505
  limit: int,
@@ -0,0 +1,28 @@
1
+ import httpx
2
+
3
+ from prediction_market_agent_tooling.gtypes import ChainID, ChecksumAddress
4
+ from prediction_market_agent_tooling.markets.seer.data_models import SeerTransaction
5
+ from prediction_market_agent_tooling.tools.datetime_utc import DatetimeUTC
6
+ from prediction_market_agent_tooling.tools.utils import to_int_timestamp, utcnow
7
+
8
+
9
+ def get_seer_transactions(
10
+ account: ChecksumAddress,
11
+ chain_id: ChainID,
12
+ start_time: DatetimeUTC | None = None,
13
+ end_time: DatetimeUTC | None = None,
14
+ timeout: int = 60, # The endpoint is pretty slow to respond atm.
15
+ ) -> list[SeerTransaction]:
16
+ url = "https://app.seer.pm/.netlify/functions/get-transactions"
17
+ params: dict[str, str | int] = {
18
+ "account": account,
19
+ "chainId": chain_id,
20
+ "startTime": to_int_timestamp(start_time) if start_time else 0,
21
+ "endTime": to_int_timestamp(end_time if end_time else utcnow()),
22
+ }
23
+ response = httpx.get(url, params=params, timeout=timeout)
24
+ response.raise_for_status()
25
+ response_json = response.json()
26
+
27
+ transactions = [SeerTransaction.model_validate(tx) for tx in response_json]
28
+ return transactions
@@ -29,10 +29,17 @@ from prediction_market_agent_tooling.markets.seer.data_models import (
29
29
  SeerMarketQuestions,
30
30
  SeerMarketWithQuestions,
31
31
  )
32
- from prediction_market_agent_tooling.markets.seer.subgraph_data_models import SeerPool
32
+ from prediction_market_agent_tooling.markets.seer.subgraph_data_models import (
33
+ SeerPool,
34
+ SwaprSwap,
35
+ )
33
36
  from prediction_market_agent_tooling.tools.hexbytes_custom import HexBytes
34
37
  from prediction_market_agent_tooling.tools.singleton import SingletonMeta
35
- from prediction_market_agent_tooling.tools.utils import to_int_timestamp, utcnow
38
+ from prediction_market_agent_tooling.tools.utils import (
39
+ DatetimeUTC,
40
+ to_int_timestamp,
41
+ utcnow,
42
+ )
36
43
  from prediction_market_agent_tooling.tools.web3_utils import unwrap_generic_value
37
44
 
38
45
 
@@ -328,8 +335,8 @@ class SeerSubgraphHandler(BaseSubgraphHandler):
328
335
  ]
329
336
  return fields
330
337
 
331
- def get_market_by_wrapped_token(self, token: ChecksumAddress) -> SeerMarket:
332
- where_stms = {"wrappedTokens_contains": [token]}
338
+ def get_market_by_wrapped_token(self, tokens: list[ChecksumAddress]) -> SeerMarket:
339
+ where_stms = {"wrappedTokens_contains": tokens}
333
340
  markets_field = self.seer_subgraph.Query.markets(
334
341
  where=unwrap_generic_value(where_stms)
335
342
  )
@@ -341,22 +348,27 @@ class SeerSubgraphHandler(BaseSubgraphHandler):
341
348
  )
342
349
  return markets[0]
343
350
 
344
- def _get_fields_for_pools(self, pools_field: FieldPath) -> list[FieldPath]:
345
- fields = [
346
- pools_field.id,
347
- pools_field.liquidity,
348
- pools_field.sqrtPrice,
349
- pools_field.token0Price,
350
- pools_field.token1Price,
351
- pools_field.token0.id,
352
- pools_field.token0.name,
353
- pools_field.token0.symbol,
354
- pools_field.token1.id,
355
- pools_field.token1.name,
356
- pools_field.token1.symbol,
357
- pools_field.totalValueLockedToken0,
358
- pools_field.totalValueLockedToken1,
351
+ def _get_fields_for_seer_token(self, fields: FieldPath) -> list[FieldPath]:
352
+ return [
353
+ fields.id,
354
+ fields.name,
355
+ fields.symbol,
359
356
  ]
357
+
358
+ def _get_fields_for_pools(self, pools_field: FieldPath) -> list[FieldPath]:
359
+ fields = (
360
+ [
361
+ pools_field.id,
362
+ pools_field.liquidity,
363
+ pools_field.sqrtPrice,
364
+ pools_field.token0Price,
365
+ pools_field.token1Price,
366
+ pools_field.totalValueLockedToken0,
367
+ pools_field.totalValueLockedToken1,
368
+ ]
369
+ + self._get_fields_for_seer_token(pools_field.token0)
370
+ + self._get_fields_for_seer_token(pools_field.token1)
371
+ )
360
372
  return fields
361
373
 
362
374
  def get_pool_by_token(
@@ -396,6 +408,41 @@ class SeerSubgraphHandler(BaseSubgraphHandler):
396
408
  return pools[0]
397
409
  return None
398
410
 
411
+ def _get_fields_for_swaps(self, swaps_field: FieldPath) -> list[FieldPath]:
412
+ fields = (
413
+ [
414
+ swaps_field.id,
415
+ swaps_field.pool.id,
416
+ swaps_field.sender,
417
+ swaps_field.recipient,
418
+ swaps_field.price,
419
+ swaps_field.amount0,
420
+ swaps_field.amount1,
421
+ swaps_field.timestamp,
422
+ ]
423
+ + self._get_fields_for_seer_token(swaps_field.token0)
424
+ + self._get_fields_for_seer_token(swaps_field.token1)
425
+ )
426
+ return fields
427
+
428
+ def get_swaps(
429
+ self,
430
+ recipient: ChecksumAddress,
431
+ timestamp_gt: DatetimeUTC | None = None,
432
+ timestamp_lt: DatetimeUTC | None = None,
433
+ ) -> list[SwaprSwap]:
434
+ where_argument: dict[str, Any] = {"recipient": recipient.lower()}
435
+ if timestamp_gt is not None:
436
+ where_argument["timestamp_gt"] = to_int_timestamp(timestamp_gt)
437
+ if timestamp_lt is not None:
438
+ where_argument["timestamp_lt"] = to_int_timestamp(timestamp_lt)
439
+
440
+ swaps_field = self.swapr_algebra_subgraph.Query.swaps(where=where_argument)
441
+ fields = self._get_fields_for_swaps(swaps_field)
442
+ swaps = self.do_query(fields=fields, pydantic_model=SwaprSwap)
443
+
444
+ return swaps
445
+
399
446
 
400
447
  class SeerQuestionsCache(metaclass=SingletonMeta):
401
448
  """A singleton cache for storing and retrieving Seer market questions.
@@ -1,12 +1,17 @@
1
1
  from pydantic import BaseModel, ConfigDict, Field
2
+ from web3 import Web3
2
3
  from web3.constants import ADDRESS_ZERO
3
4
 
4
5
  from prediction_market_agent_tooling.gtypes import (
6
+ ChecksumAddress,
5
7
  CollateralToken,
6
8
  HexAddress,
7
9
  HexBytes,
8
10
  OutcomeStr,
11
+ OutcomeToken,
12
+ Wei,
9
13
  )
14
+ from prediction_market_agent_tooling.tools.datetime_utc import DatetimeUTC
10
15
 
11
16
 
12
17
  class SeerToken(BaseModel):
@@ -14,6 +19,10 @@ class SeerToken(BaseModel):
14
19
  name: str
15
20
  symbol: str
16
21
 
22
+ @property
23
+ def address(self) -> ChecksumAddress:
24
+ return Web3.to_checksum_address(self.id.hex())
25
+
17
26
 
18
27
  class SeerPool(BaseModel):
19
28
  model_config = ConfigDict(populate_by_name=True)
@@ -28,6 +37,62 @@ class SeerPool(BaseModel):
28
37
  totalValueLockedToken1: float
29
38
 
30
39
 
40
+ class SwaprSwap(BaseModel):
41
+ id: str # It's like "0x73afd8f096096552d72a0b40ea66d2076be136c6a531e2f6b190d151a750271e#32" (note the #32) # web3-private-key-ok
42
+ recipient: HexAddress
43
+ sender: HexAddress
44
+ price: Wei
45
+ amount0: CollateralToken
46
+ amount1: CollateralToken
47
+ token0: SeerToken
48
+ token1: SeerToken
49
+ timestamp: int
50
+
51
+ @property
52
+ def timestamp_utc(self) -> DatetimeUTC:
53
+ return DatetimeUTC.to_datetime_utc(self.timestamp)
54
+
55
+ @property
56
+ def added_to_pool(self) -> CollateralToken:
57
+ return self.amount0 if self.amount0 > 0 else self.amount1
58
+
59
+ @property
60
+ def withdrawn_from_pool(self) -> OutcomeToken:
61
+ return (
62
+ OutcomeToken(abs(self.amount0).value)
63
+ if self.amount0 < 0
64
+ else OutcomeToken(abs(self.amount1).value)
65
+ )
66
+
67
+
68
+ class SeerSwap(BaseModel):
69
+ id: str # It's like "0x73afd8f096096552d72a0b40ea66d2076be136c6a531e2f6b190d151a750271e#32" (note the #32) # web3-private-key-ok
70
+ recipient: HexAddress
71
+ sender: HexAddress
72
+ price: Wei
73
+ amount0: CollateralToken
74
+ amount1: CollateralToken
75
+ token0: SeerToken
76
+ token1: SeerToken
77
+ timestamp: int
78
+
79
+ @property
80
+ def timestamp_utc(self) -> DatetimeUTC:
81
+ return DatetimeUTC.to_datetime_utc(self.timestamp)
82
+
83
+ @property
84
+ def buying_collateral_amount(self) -> CollateralToken:
85
+ return self.amount0 if self.amount0 > 0 else self.amount1
86
+
87
+ @property
88
+ def received_shares_amount(self) -> OutcomeToken:
89
+ return (
90
+ OutcomeToken(abs(self.amount0).value)
91
+ if self.amount0 < 0
92
+ else OutcomeToken(abs(self.amount1).value)
93
+ )
94
+
95
+
31
96
  class NewMarketEvent(BaseModel):
32
97
  market: HexAddress
33
98
  marketName: str
@@ -1,4 +1,6 @@
1
+ from enum import Enum
1
2
  from itertools import chain
3
+ from typing import Callable
2
4
 
3
5
  import numpy as np
4
6
  from scipy.optimize import minimize
@@ -9,11 +11,7 @@ from prediction_market_agent_tooling.gtypes import (
9
11
  Probability,
10
12
  )
11
13
  from prediction_market_agent_tooling.loggers import logger
12
- from prediction_market_agent_tooling.markets.agent_market import AgentMarket
13
14
  from prediction_market_agent_tooling.markets.market_fees import MarketFees
14
- from prediction_market_agent_tooling.markets.omen.omen import (
15
- calculate_buy_outcome_token,
16
- )
17
15
  from prediction_market_agent_tooling.tools.betting_strategies.utils import (
18
16
  BinaryKellyBet,
19
17
  CategoricalKellyBet,
@@ -21,6 +19,11 @@ from prediction_market_agent_tooling.tools.betting_strategies.utils import (
21
19
  from prediction_market_agent_tooling.tools.utils import check_not_none
22
20
 
23
21
 
22
+ class KellyType(str, Enum):
23
+ SIMPLE = "simple"
24
+ FULL = "full"
25
+
26
+
24
27
  def check_is_valid_probability(probability: float) -> None:
25
28
  if not 0 <= probability <= 1:
26
29
  raise ValueError("Probability must be between 0 and 1")
@@ -237,7 +240,7 @@ def get_kelly_bets_categorical_simplified(
237
240
 
238
241
 
239
242
  def get_kelly_bets_categorical_full(
240
- outcome_pool_sizes: list[OutcomeToken],
243
+ market_probabilities: list[Probability],
241
244
  estimated_probabilities: list[Probability],
242
245
  confidence: float,
243
246
  max_bet: CollateralToken,
@@ -245,6 +248,8 @@ def get_kelly_bets_categorical_full(
245
248
  allow_multiple_bets: bool,
246
249
  allow_shorting: bool,
247
250
  multicategorical: bool,
251
+ # investment amount, outcome index --> received outcome tokens
252
+ get_buy_token_amount: Callable[[CollateralToken, int], OutcomeToken],
248
253
  bet_precision: int = 6,
249
254
  ) -> list[CategoricalKellyBet]:
250
255
  """
@@ -255,18 +260,13 @@ def get_kelly_bets_categorical_full(
255
260
  If the agent's probabilities are very close to the market's, returns all-zero bets.
256
261
  multicategorical means that multiple outcomes could be selected as correct ones.
257
262
  """
258
- assert len(outcome_pool_sizes) == len(
263
+ assert len(market_probabilities) == len(
259
264
  estimated_probabilities
260
265
  ), "Mismatch in number of outcomes"
261
-
262
- market_probabilities = AgentMarket.compute_fpmm_probabilities(
263
- [x.as_outcome_wei for x in outcome_pool_sizes]
264
- )
265
-
266
266
  for p in chain(market_probabilities, estimated_probabilities, [confidence]):
267
267
  check_is_valid_probability(p)
268
268
 
269
- n = len(outcome_pool_sizes)
269
+ n = len(market_probabilities)
270
270
  max_bet_value = max_bet.value
271
271
 
272
272
  if all(
@@ -283,22 +283,17 @@ def get_kelly_bets_categorical_full(
283
283
  payout = 0.0
284
284
  if bets[i] >= 0:
285
285
  # If bet on i is positive, we buy outcome i
286
- buy_result = calculate_buy_outcome_token(
287
- CollateralToken(bets[i]), i, outcome_pool_sizes, fees
288
- )
289
- payout += buy_result.outcome_tokens_received.value
286
+ buy_result = get_buy_token_amount(CollateralToken(bets[i]), i)
287
+ payout += buy_result.value
290
288
  else:
291
289
  # If bet is negative, we "short" outcome i by buying all other outcomes
292
290
  for j in range(n):
293
291
  if j == i:
294
292
  continue
295
- buy_result = calculate_buy_outcome_token(
296
- CollateralToken(abs(bets[i]) / (n - 1)),
297
- j,
298
- outcome_pool_sizes,
299
- fees,
293
+ buy_result = get_buy_token_amount(
294
+ CollateralToken(abs(bets[i]) / (n - 1)), j
300
295
  )
301
- payout += buy_result.outcome_tokens_received.value
296
+ payout += buy_result.value
302
297
  payouts.append(payout)
303
298
  return payouts
304
299
 
@@ -34,8 +34,20 @@ class DatetimeUTC(datetime):
34
34
  def __get_pydantic_core_schema__(
35
35
  cls, source_type: t.Any, handler: GetCoreSchemaHandler
36
36
  ) -> CoreSchema:
37
- dt_schema = handler(datetime)
38
- return core_schema.no_info_after_validator_function(cls._validate, dt_schema)
37
+ # Use union schema to handle int, str, and datetime inputs directly
38
+ return core_schema.union_schema(
39
+ [
40
+ core_schema.no_info_after_validator_function(
41
+ cls._validate, core_schema.int_schema()
42
+ ),
43
+ core_schema.no_info_after_validator_function(
44
+ cls._validate, core_schema.str_schema()
45
+ ),
46
+ core_schema.no_info_after_validator_function(
47
+ cls._validate, handler(datetime)
48
+ ),
49
+ ]
50
+ )
39
51
 
40
52
  @staticmethod
41
53
  def from_datetime(dt: datetime) -> "DatetimeUTC":
@@ -5,7 +5,9 @@ from langfuse import Langfuse
5
5
  from langfuse.client import TraceWithDetails
6
6
  from pydantic import BaseModel
7
7
 
8
+ from prediction_market_agent_tooling.deploy.agent import MarketType
8
9
  from prediction_market_agent_tooling.loggers import logger
10
+ from prediction_market_agent_tooling.markets.agent_market import AgentMarket
9
11
  from prediction_market_agent_tooling.markets.data_models import (
10
12
  CategoricalProbabilisticAnswer,
11
13
  PlacedTrade,
@@ -16,12 +18,13 @@ from prediction_market_agent_tooling.markets.omen.omen import OmenAgentMarket
16
18
  from prediction_market_agent_tooling.markets.omen.omen_constants import (
17
19
  WRAPPED_XDAI_CONTRACT_ADDRESS,
18
20
  )
21
+ from prediction_market_agent_tooling.markets.seer.seer import SeerAgentMarket
19
22
  from prediction_market_agent_tooling.tools.utils import DatetimeUTC
20
23
 
21
24
 
22
25
  class ProcessMarketTrace(BaseModel):
23
26
  timestamp: int
24
- market: OmenAgentMarket
27
+ market: SeerAgentMarket | OmenAgentMarket
25
28
  answer: CategoricalProbabilisticAnswer
26
29
  trades: list[PlacedTrade]
27
30
 
@@ -40,13 +43,18 @@ class ProcessMarketTrace(BaseModel):
40
43
  def from_langfuse_trace(
41
44
  trace: TraceWithDetails,
42
45
  ) -> t.Optional["ProcessMarketTrace"]:
43
- market = trace_to_omen_agent_market(trace)
46
+ market = trace_to_agent_market(trace)
44
47
  answer = trace_to_answer(trace)
45
48
  trades = trace_to_trades(trace)
46
49
 
47
50
  if not market or not answer or not trades:
48
51
  return None
49
52
 
53
+ if not isinstance(market, (SeerAgentMarket, OmenAgentMarket)):
54
+ raise ValueError(
55
+ f"Market type {type(market)} is not supported for ProcessMarketTrace"
56
+ )
57
+
50
58
  return ProcessMarketTrace(
51
59
  market=market,
52
60
  answer=answer,
@@ -105,17 +113,21 @@ def get_traces_for_agent(
105
113
  return all_agent_traces
106
114
 
107
115
 
108
- def trace_to_omen_agent_market(trace: TraceWithDetails) -> OmenAgentMarket | None:
116
+ def trace_to_agent_market(trace: TraceWithDetails) -> AgentMarket | None:
109
117
  if not trace.input:
110
118
  logger.warning(f"No input in the trace: {trace}")
111
119
  return None
112
120
  if not trace.input["args"]:
113
121
  logger.warning(f"No args in the trace: {trace}")
114
122
  return None
115
- assert len(trace.input["args"]) == 2 and trace.input["args"][0] == "omen"
123
+ assert len(trace.input["args"]) == 2
124
+
125
+ market_type = MarketType(trace.input["args"][0])
126
+ market_class = market_type.market_class
127
+
116
128
  try:
117
129
  # If the market model is invalid (e.g. outdated), it will raise an exception
118
- market = OmenAgentMarket.model_validate(trace.input["args"][1])
130
+ market = market_class.model_validate(trace.input["args"][1])
119
131
  return market
120
132
  except Exception as e:
121
133
  logger.warning(f"Market not parsed from langfuse because: {e}")
@@ -194,7 +194,7 @@ def mint_full_set(
194
194
  # of the child market.
195
195
  seer_subgraph_handler = SeerSubgraphHandler()
196
196
  market = seer_subgraph_handler.get_market_by_wrapped_token(
197
- token=collateral_token_contract.address
197
+ tokens=[collateral_token_contract.address]
198
198
  )
199
199
  market_collateral_token = Web3.to_checksum_address(market.collateral_token)
200
200
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: prediction-market-agent-tooling
3
- Version: 0.68.1
3
+ Version: 0.69.0
4
4
  Summary: Tools to benchmark, deploy and monitor prediction market agents.
5
5
  Author: Gnosis
6
6
  Requires-Python: >=3.10,<3.13
@@ -27,9 +27,9 @@ prediction_market_agent_tooling/benchmark/utils.py,sha256=vmVTQLER8I7MM_bHFiavrN
27
27
  prediction_market_agent_tooling/chains.py,sha256=MF600uUR_I0Mf7-Pz5InS6RWMkdrZI2sohP6Vfh0YRw,148
28
28
  prediction_market_agent_tooling/config.py,sha256=juGITh2OxpH_dVe-63W4vcrRYtzldSUTT7UIjcDvAT4,13006
29
29
  prediction_market_agent_tooling/data_download/langfuse_data_downloader.py,sha256=VY23h324VKIVkevj1B1O-zL1eEp9AElmcfn6SwYDUSc,14246
30
- prediction_market_agent_tooling/deploy/agent.py,sha256=LQUfP25ViMBQTqy9l_u0WsML__PsPMqjt1m1dXvW5zM,30076
30
+ prediction_market_agent_tooling/deploy/agent.py,sha256=xtufEkxHkR54TSAqRqiBRcISf05MwrV-WlG4PcOo7xM,30582
31
31
  prediction_market_agent_tooling/deploy/agent_example.py,sha256=yS1fWkHynr9MYGNOM2WsCnRWLPaffY4bOc6bIudrdd4,1377
32
- prediction_market_agent_tooling/deploy/betting_strategy.py,sha256=E_0P8MFBtm50OT9BCAy_ZSHW80skDPtSr-vgGZqNjDs,26584
32
+ prediction_market_agent_tooling/deploy/betting_strategy.py,sha256=C6Q2tfUllkC9fIaABhKyVjq5QTO6qnVd1Jd1GFePaCU,29553
33
33
  prediction_market_agent_tooling/deploy/constants.py,sha256=iobTlZpQD6_2UL9TfoElAnBEbqzIIAKZSsAoMCGhwmA,331
34
34
  prediction_market_agent_tooling/deploy/gcp/deploy.py,sha256=CYUgnfy-9XVk04kkxA_5yp0GE9Mw5caYqlFUZQ2j3ks,3739
35
35
  prediction_market_agent_tooling/deploy/gcp/kubernetes_models.py,sha256=OsPboCFGiZKsvGyntGZHwdqPlLTthITkNF5rJFvGgU8,2582
@@ -38,10 +38,10 @@ prediction_market_agent_tooling/deploy/trade_interval.py,sha256=Xk9j45alQ_vrasGv
38
38
  prediction_market_agent_tooling/gtypes.py,sha256=bUIZfZIGvIi3aiZNu5rVE9kRevw8sfMa4bcze6QeBg8,6058
39
39
  prediction_market_agent_tooling/jobs/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
40
40
  prediction_market_agent_tooling/jobs/jobs_models.py,sha256=4KZ0iaKZx5zEZBBiEo9CHk0OpX3bKjoYA0wmptR0Wyk,2455
41
- prediction_market_agent_tooling/jobs/omen/omen_jobs.py,sha256=DqWQTQUsS0KE97mEJNeDxRcqcEktPavqOpkXZ21Mn0M,5126
41
+ prediction_market_agent_tooling/jobs/omen/omen_jobs.py,sha256=CojpgghRs0lFEf5wl-oWhGaQozG616YD0cXdlmFO-U0,5116
42
42
  prediction_market_agent_tooling/loggers.py,sha256=o1HyvwtK1DbuC0YWQwJNqzXLLbSC41gNBkEUxiAziEg,5796
43
43
  prediction_market_agent_tooling/logprobs_parser.py,sha256=DBlBQtWX8_URXhzTU3YWIPa76Zx3QDHlx1ARqbgJsVI,5008
44
- prediction_market_agent_tooling/markets/agent_market.py,sha256=x4BW--A9gYWwd0qnBtG6D4fktnD695Ej_GYdJ_b5lqM,21501
44
+ prediction_market_agent_tooling/markets/agent_market.py,sha256=AZpL6_vicDexs6YhAqLDCdxuIuF_oYfQnoqgBzOBf0Q,21755
45
45
  prediction_market_agent_tooling/markets/base_subgraph_handler.py,sha256=7RaYO_4qAmQ6ZGM8oPK2-CkiJfKmV9MxM-rJlduaecU,1971
46
46
  prediction_market_agent_tooling/markets/blockchain_utils.py,sha256=OJp402xDbPXfks-S3iRN2ygAjjJpdasKVt6iYvQlemA,3065
47
47
  prediction_market_agent_tooling/markets/categorize.py,sha256=orLZlPaHgeREU66m1amxfWikeV77idV4sZDPB8NgSD0,1300
@@ -52,7 +52,7 @@ prediction_market_agent_tooling/markets/manifold/data_models.py,sha256=3z1gFbPME
52
52
  prediction_market_agent_tooling/markets/manifold/manifold.py,sha256=hHloNKYQRyd4OWTCUh5kCk-Kx1m77rOIg8d8bGJ6jvk,5454
53
53
  prediction_market_agent_tooling/markets/manifold/utils.py,sha256=DCigEbpGWkXR-RZT_oJrvJhilmxKFAxcWMjUFi0nBBI,530
54
54
  prediction_market_agent_tooling/markets/market_fees.py,sha256=YeK3ynjYIguB0xf6sO5iyg9lOdW_HD4C6nbJfiGyRCU,1351
55
- prediction_market_agent_tooling/markets/markets.py,sha256=jEyYZVubrxnMvPHVO3ofxVXgzwmQ5sPlPx4QAVLy9qc,2777
55
+ prediction_market_agent_tooling/markets/markets.py,sha256=GmloYxwLJC51v65-ooQ5DqMtKpSCZ955TZWkf0uxhLI,3256
56
56
  prediction_market_agent_tooling/markets/metaculus/api.py,sha256=4TRPGytQQbSdf42DCg2M_JWYPAuNjqZ3eBqaQBLkNks,2736
57
57
  prediction_market_agent_tooling/markets/metaculus/data_models.py,sha256=WjPt0MKeJNtoY-8oLQTLC8vQYYQ-dBj8UZoPq-UBYsQ,3288
58
58
  prediction_market_agent_tooling/markets/metaculus/metaculus.py,sha256=kM0U0k90YfLE82ra53JAoCR9uIc9wm4B8l32hLw5imU,5312
@@ -72,18 +72,19 @@ prediction_market_agent_tooling/markets/polymarket/polymarket.py,sha256=-zAx64Mc
72
72
  prediction_market_agent_tooling/markets/polymarket/polymarket_contracts.py,sha256=x8yvAUPl55nbJALsQAAHJtstahhpH8WcEOj8or_QloQ,1165
73
73
  prediction_market_agent_tooling/markets/polymarket/polymarket_subgraph_handler.py,sha256=2b1hVAp3tCPkmp_FFAZwy6baW7CrPhpUSGk7TlrvgtU,1631
74
74
  prediction_market_agent_tooling/markets/polymarket/utils.py,sha256=A_diygWKYyp4WHbxAlAVAuC0S0ZqbEKE80wxL6mxHKQ,1275
75
- prediction_market_agent_tooling/markets/seer/data_models.py,sha256=M6hCKCAHPqKGh8ST0TVvRcCbNfJ-e60jetVS49Zu87w,6687
75
+ prediction_market_agent_tooling/markets/seer/data_models.py,sha256=Y8tIyOyTHfcIYu0b6YfMDxXcDnW87o3Fam4NEHBITmw,7833
76
76
  prediction_market_agent_tooling/markets/seer/exceptions.py,sha256=cEObdjluivD94tgOLzmimR7wgQEOt6SRakrYdhsRQtk,112
77
77
  prediction_market_agent_tooling/markets/seer/price_manager.py,sha256=LCfbnwOlcWn_pb5mNSqu0fYb-FKOgql19OnpItEc8wA,10007
78
- prediction_market_agent_tooling/markets/seer/seer.py,sha256=gv3iDH9g9JSjeh2IHTmrH1qG2osINxKpHI4BN76nbb8,29319
78
+ prediction_market_agent_tooling/markets/seer/seer.py,sha256=UUH75JAMfI_uZE1pEbdI3rbf5c0K0KgUzBiaVRshjV0,31031
79
+ prediction_market_agent_tooling/markets/seer/seer_api.py,sha256=4iiTWskxWm__oGgUUd1GhV_ItPSrAr0OfnYQ7lKndnQ,1143
79
80
  prediction_market_agent_tooling/markets/seer/seer_contracts.py,sha256=JqfQNFSRWREPw6pQGpJoh-No5ZlKwmTiALJiAYEuvW8,5516
80
- prediction_market_agent_tooling/markets/seer/seer_subgraph_handler.py,sha256=e-rI0JwJAGtrFmUoDZP5uFbMx3EPmUHAhk2RgQvx3u4,17269
81
- prediction_market_agent_tooling/markets/seer/subgraph_data_models.py,sha256=VREm8bR-gPY06ODFJsaiSMinYgN64q5dC6DFckMY6oA,1831
81
+ prediction_market_agent_tooling/markets/seer/seer_subgraph_handler.py,sha256=aohhV-VA2fsmmRpZSSsxUHpXW78wWzXvF2l2vqPsupI,18791
82
+ prediction_market_agent_tooling/markets/seer/subgraph_data_models.py,sha256=u75DaEwQCl77eko2eNuUP8dJPBXIaBrERR8bLNeMdiM,3753
82
83
  prediction_market_agent_tooling/markets/seer/swap_pool_handler.py,sha256=k_sCEJZLroVDjOVkZ084VKJGNODLGjBGezhsWEZvlH4,3528
83
84
  prediction_market_agent_tooling/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
84
85
  prediction_market_agent_tooling/tools/_generic_value.py,sha256=pD_PI13lpPp1gFoljHwa_Lzlp-u2pu0m-Z7LcxwDM2U,10618
85
86
  prediction_market_agent_tooling/tools/balances.py,sha256=Osab21btfJDw2Y-jT_TV-KHGrseCRxcsYeW6WcOMB8E,1050
86
- prediction_market_agent_tooling/tools/betting_strategies/kelly_criterion.py,sha256=C1rWbj88gPsBYVpKSAoJgZ1iVfOL2YJ5tXnvqIxmMKk,14716
87
+ prediction_market_agent_tooling/tools/betting_strategies/kelly_criterion.py,sha256=o5ba633gKiDqV4t_C2d9FWwH-HkRAOZd8FcZTYvbj6g,14451
87
88
  prediction_market_agent_tooling/tools/betting_strategies/stretch_bet_between.py,sha256=THMXwFlskvzbjnX_OiYtDSzI8XVFyULWfP2525_9UGc,429
88
89
  prediction_market_agent_tooling/tools/betting_strategies/utils.py,sha256=MpS3FOMn0C7nbmbQRUT9QwSh3UzzsgGczP91iSMr9wo,261
89
90
  prediction_market_agent_tooling/tools/caches/db_cache.py,sha256=rZIGhgijquwwPtp_qncSAPR1SDF2XxIVZL1ir0fgzWw,12127
@@ -95,7 +96,7 @@ prediction_market_agent_tooling/tools/cow/cow_order.py,sha256=q39Kr0mSvnKjyQ4LSm
95
96
  prediction_market_agent_tooling/tools/cow/models.py,sha256=Z_XUAMj5AGHZE-A92oRG0DuPeXTSyWnZRIwKQ49LaXU,709
96
97
  prediction_market_agent_tooling/tools/cow/semaphore.py,sha256=IJGKRgvXnRSkEt_z1i5-eFIoVMcyhr7HK0JfIz1MXdQ,3738
97
98
  prediction_market_agent_tooling/tools/custom_exceptions.py,sha256=Fh8z1fbwONvP4-j7AmV_PuEcoqb6-QXa9PJ9m7guMcM,93
98
- prediction_market_agent_tooling/tools/datetime_utc.py,sha256=8_WackjtjC8zHXrhQFTGQ6e6Fz_6llWoKR4CSFvIv9I,2766
99
+ prediction_market_agent_tooling/tools/datetime_utc.py,sha256=_oO6mMc28C9aSmQfrG-S7UQy5uMHVEQqia8arnscVCk,3213
99
100
  prediction_market_agent_tooling/tools/db/db_manager.py,sha256=GtzHH1NLl8HwqC8Z7s6eTlIQXuV0blxfaV2PeQrBnfQ,3013
100
101
  prediction_market_agent_tooling/tools/google_utils.py,sha256=D-6FB2HRtmxaKZJ_Za-qj6VZCp5XU18rF4wLMMrqEUg,2557
101
102
  prediction_market_agent_tooling/tools/hexbytes_custom.py,sha256=0Y1n8MChjWJmE4w6kgIJPiqbrGULui0m3TQzUBgZDHs,2206
@@ -106,7 +107,7 @@ prediction_market_agent_tooling/tools/ipfs/ipfs_handler.py,sha256=CTTMfTvs_8PH4k
106
107
  prediction_market_agent_tooling/tools/is_invalid.py,sha256=ArYgPqZApRwIPXwBmTdz7d-7ynP856GLaeCcIl9tV08,5334
107
108
  prediction_market_agent_tooling/tools/is_predictable.py,sha256=RNwVH31qpHtg2UWdeMZoFOpMsKgE-aKmcc3Uj6_dK-A,6909
108
109
  prediction_market_agent_tooling/tools/langfuse_.py,sha256=jI_4ROxqo41CCnWGS1vN_AeDVhRzLMaQLxH3kxDu3L8,1153
109
- prediction_market_agent_tooling/tools/langfuse_client_utils.py,sha256=D_AzGvEg-YXc0xhYGALGPnmtQqeNyNxkP0SWMCRQW3M,6644
110
+ prediction_market_agent_tooling/tools/langfuse_client_utils.py,sha256=8d33dcYx5ElyE0pL_VTJTcJIXvt3PPkDaadQgxGGyp0,7132
110
111
  prediction_market_agent_tooling/tools/omen/reality_accuracy.py,sha256=M1SF7iSW1gVlQSTskdVFTn09uPLST23YeipVIWj54io,2236
111
112
  prediction_market_agent_tooling/tools/omen/sell_positions.py,sha256=Q4oI7_QI3AkyxlH10VvxDahYVrphQa1Wnox2Ce_cf_k,2452
112
113
  prediction_market_agent_tooling/tools/parallelism.py,sha256=6Gou0hbjtMZrYvxjTDFUDZuxmE2nqZVbb6hkg1hF82A,1022
@@ -122,7 +123,7 @@ prediction_market_agent_tooling/tools/singleton.py,sha256=CHpUJuu89Hwup2fH9CIChU
122
123
  prediction_market_agent_tooling/tools/streamlit_user_login.py,sha256=NXEqfjT9Lc9QtliwSGRASIz1opjQ7Btme43H4qJbzgE,3010
123
124
  prediction_market_agent_tooling/tools/tavily/tavily_models.py,sha256=5ldQs1pZe6uJ5eDAuP4OLpzmcqYShlIV67kttNFvGS0,342
124
125
  prediction_market_agent_tooling/tools/tavily/tavily_search.py,sha256=pPs0qZNfJ7G-1ajfz0iaWOBQyiC0TbcShfrW8T39jtg,3859
125
- prediction_market_agent_tooling/tools/tokens/auto_deposit.py,sha256=ccpDCYueObaaoBS66d3P-xprJg7FFZREdx-6oRbuKm8,9183
126
+ prediction_market_agent_tooling/tools/tokens/auto_deposit.py,sha256=E4jq6HvwOQCt6hb6yEFps7G78Ri5Z0JOfh3UqrwLPEM,9186
126
127
  prediction_market_agent_tooling/tools/tokens/auto_withdraw.py,sha256=HYLSI_MincuyRGSrSJTuf0cHMcdnyQPrXOUW3o2quBk,2811
127
128
  prediction_market_agent_tooling/tools/tokens/main_token.py,sha256=1rbwpdCusPgQIVFuo3m00nBZ_b2lCAoFVm67i-YDcEw,812
128
129
  prediction_market_agent_tooling/tools/tokens/slippage.py,sha256=ByBssRY6fBjLdxPx5bpudYfs-Z4gUa8CyOGuLDd5tQk,639
@@ -131,8 +132,8 @@ prediction_market_agent_tooling/tools/tokens/usd.py,sha256=DPO-4HBTy1-TZHKL_9CnH
131
132
  prediction_market_agent_tooling/tools/transaction_cache.py,sha256=K5YKNL2_tR10Iw2TD9fuP-CTGpBbZtNdgbd0B_R7pjg,1814
132
133
  prediction_market_agent_tooling/tools/utils.py,sha256=mbOGoWKalNIm7M2K51TEPGwU9oVp1Z6SPsFaBgbn6ws,7397
133
134
  prediction_market_agent_tooling/tools/web3_utils.py,sha256=ajHZIBM_PNNNUp0ZxJ-AvpSzyVATm7LnZtb2hi6NcOw,12614
134
- prediction_market_agent_tooling-0.68.1.dist-info/LICENSE,sha256=6or154nLLU6bELzjh0mCreFjt0m2v72zLi3yHE0QbeE,7650
135
- prediction_market_agent_tooling-0.68.1.dist-info/METADATA,sha256=xViLluiOrvDpEF2wBMDodVyGSTS9auN_vTBosi8reos,8805
136
- prediction_market_agent_tooling-0.68.1.dist-info/WHEEL,sha256=b4K_helf-jlQoXBBETfwnf4B04YC67LOev0jo4fX5m8,88
137
- prediction_market_agent_tooling-0.68.1.dist-info/entry_points.txt,sha256=m8PukHbeH5g0IAAmOf_1Ahm-sGAMdhSSRQmwtpmi2s8,81
138
- prediction_market_agent_tooling-0.68.1.dist-info/RECORD,,
135
+ prediction_market_agent_tooling-0.69.0.dist-info/LICENSE,sha256=6or154nLLU6bELzjh0mCreFjt0m2v72zLi3yHE0QbeE,7650
136
+ prediction_market_agent_tooling-0.69.0.dist-info/METADATA,sha256=UtvkCNpHOorBV50gdyXVdgBJq3EiN15wH8SdyzeAt08,8805
137
+ prediction_market_agent_tooling-0.69.0.dist-info/WHEEL,sha256=b4K_helf-jlQoXBBETfwnf4B04YC67LOev0jo4fX5m8,88
138
+ prediction_market_agent_tooling-0.69.0.dist-info/entry_points.txt,sha256=m8PukHbeH5g0IAAmOf_1Ahm-sGAMdhSSRQmwtpmi2s8,81
139
+ prediction_market_agent_tooling-0.69.0.dist-info/RECORD,,