prediction-market-agent-tooling 0.67.4__py3-none-any.whl → 0.68.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 (18) hide show
  1. prediction_market_agent_tooling/deploy/agent.py +2 -4
  2. prediction_market_agent_tooling/deploy/betting_strategy.py +177 -49
  3. prediction_market_agent_tooling/jobs/omen/omen_jobs.py +2 -2
  4. prediction_market_agent_tooling/markets/omen/data_models.py +6 -1
  5. prediction_market_agent_tooling/markets/omen/omen.py +43 -6
  6. prediction_market_agent_tooling/markets/seer/price_manager.py +68 -0
  7. prediction_market_agent_tooling/markets/seer/seer.py +11 -7
  8. prediction_market_agent_tooling/markets/seer/seer_subgraph_handler.py +2 -0
  9. prediction_market_agent_tooling/markets/seer/subgraph_data_models.py +2 -0
  10. prediction_market_agent_tooling/tools/betting_strategies/kelly_criterion.py +276 -8
  11. prediction_market_agent_tooling/tools/betting_strategies/utils.py +6 -1
  12. prediction_market_agent_tooling/tools/langfuse_client_utils.py +0 -3
  13. prediction_market_agent_tooling/tools/web3_utils.py +2 -0
  14. {prediction_market_agent_tooling-0.67.4.dist-info → prediction_market_agent_tooling-0.68.0.dist-info}/METADATA +1 -1
  15. {prediction_market_agent_tooling-0.67.4.dist-info → prediction_market_agent_tooling-0.68.0.dist-info}/RECORD +18 -18
  16. {prediction_market_agent_tooling-0.67.4.dist-info → prediction_market_agent_tooling-0.68.0.dist-info}/LICENSE +0 -0
  17. {prediction_market_agent_tooling-0.67.4.dist-info → prediction_market_agent_tooling-0.68.0.dist-info}/WHEEL +0 -0
  18. {prediction_market_agent_tooling-0.67.4.dist-info → prediction_market_agent_tooling-0.68.0.dist-info}/entry_points.txt +0 -0
@@ -11,7 +11,7 @@ from pydantic_ai.exceptions import UnexpectedModelBehavior
11
11
  from prediction_market_agent_tooling.config import APIKeys
12
12
  from prediction_market_agent_tooling.deploy.betting_strategy import (
13
13
  BettingStrategy,
14
- MultiCategoricalMaxAccuracyBettingStrategy,
14
+ CategoricalMaxAccuracyBettingStrategy,
15
15
  TradeType,
16
16
  )
17
17
  from prediction_market_agent_tooling.deploy.trade_interval import (
@@ -662,9 +662,7 @@ class DeployableTraderAgent(DeployablePredictionAgent):
662
662
  Given the market and prediction, agent uses this method to calculate optimal outcome and bet size.
663
663
  """
664
664
  total_amount = self.get_total_amount_to_bet(market)
665
- return MultiCategoricalMaxAccuracyBettingStrategy(
666
- max_position_amount=total_amount
667
- )
665
+ return CategoricalMaxAccuracyBettingStrategy(max_position_amount=total_amount)
668
666
 
669
667
  def build_trades(
670
668
  self,
@@ -30,8 +30,13 @@ from prediction_market_agent_tooling.markets.omen.omen import (
30
30
  from prediction_market_agent_tooling.tools.betting_strategies.kelly_criterion import (
31
31
  get_kelly_bet_full,
32
32
  get_kelly_bet_simplified,
33
+ get_kelly_bets_categorical_full,
34
+ get_kelly_bets_categorical_simplified,
35
+ )
36
+ from prediction_market_agent_tooling.tools.betting_strategies.utils import (
37
+ BinaryKellyBet,
38
+ CategoricalKellyBet,
33
39
  )
34
- from prediction_market_agent_tooling.tools.betting_strategies.utils import SimpleBet
35
40
  from prediction_market_agent_tooling.tools.utils import check_not_none
36
41
 
37
42
 
@@ -245,7 +250,7 @@ class BettingStrategy(ABC):
245
250
  return trades
246
251
 
247
252
 
248
- class MultiCategoricalMaxAccuracyBettingStrategy(BettingStrategy):
253
+ class CategoricalMaxAccuracyBettingStrategy(BettingStrategy):
249
254
  def __init__(self, max_position_amount: USD, take_profit: bool = True):
250
255
  super().__init__(take_profit=take_profit)
251
256
  self.max_position_amount = max_position_amount
@@ -308,8 +313,11 @@ class MultiCategoricalMaxAccuracyBettingStrategy(BettingStrategy):
308
313
  )
309
314
  return trades
310
315
 
316
+ def __repr__(self) -> str:
317
+ return f"CategoricalMaxAccuracyBettingStrategy(max_position_amount={self.max_position_amount}, take_profit={self.take_profit})"
318
+
311
319
 
312
- class MaxExpectedValueBettingStrategy(MultiCategoricalMaxAccuracyBettingStrategy):
320
+ class MaxExpectedValueBettingStrategy(CategoricalMaxAccuracyBettingStrategy):
313
321
  @staticmethod
314
322
  def calculate_direction(
315
323
  market: AgentMarket, answer: CategoricalProbabilisticAnswer
@@ -350,49 +358,52 @@ class MaxExpectedValueBettingStrategy(MultiCategoricalMaxAccuracyBettingStrategy
350
358
 
351
359
  return best_outcome
352
360
 
361
+ def __repr__(self) -> str:
362
+ return f"MaxExpectedValueBettingStrategy(max_position_amount={self.max_position_amount}, take_profit={self.take_profit})"
363
+
353
364
 
354
- class KellyBettingStrategy(BettingStrategy):
365
+ class BinaryKellyBettingStrategy(BettingStrategy):
355
366
  def __init__(
356
367
  self,
357
368
  max_position_amount: USD,
358
369
  max_price_impact: float | None = None,
359
370
  take_profit: bool = True,
371
+ force_simplified_calculation: bool = False,
360
372
  ):
361
373
  super().__init__(take_profit=take_profit)
362
374
  self.max_position_amount = max_position_amount
363
375
  self.max_price_impact = max_price_impact
376
+ self.force_simplified_calculation = force_simplified_calculation
364
377
 
365
378
  @property
366
379
  def maximum_possible_bet_amount(self) -> USD:
367
380
  return self.max_position_amount
368
381
 
369
- @staticmethod
370
382
  def get_kelly_bet(
383
+ self,
371
384
  market: AgentMarket,
372
- max_bet_amount: USD,
373
385
  direction: OutcomeStr,
374
386
  other_direction: OutcomeStr,
375
387
  answer: CategoricalProbabilisticAnswer,
376
388
  override_p_yes: float | None = None,
377
- ) -> SimpleBet:
389
+ ) -> BinaryKellyBet:
390
+ if not market.is_binary:
391
+ raise ValueError("This strategy is usable only with binary markets.")
392
+
378
393
  estimated_p_yes = (
379
394
  answer.probability_for_market_outcome(direction)
380
395
  if not override_p_yes
381
396
  else override_p_yes
382
397
  )
383
398
 
384
- if not market.is_binary:
385
- # use Kelly simple, since Kelly full only supports 2 outcomes
386
-
399
+ if market.outcome_token_pool is None or self.force_simplified_calculation:
387
400
  kelly_bet = get_kelly_bet_simplified(
388
- max_bet=market.get_usd_in_token(max_bet_amount),
401
+ max_bet=market.get_usd_in_token(self.max_position_amount),
389
402
  market_p_yes=market.probability_for_market_outcome(direction),
390
403
  estimated_p_yes=estimated_p_yes,
391
404
  confidence=answer.confidence,
392
405
  )
393
406
  else:
394
- # We consider only binary markets, since the Kelly strategy is not yet implemented
395
- # for markets with more than 2 outcomes (https://github.com/gnosis/prediction-market-agent-tooling/issues/671).
396
407
  direction_to_bet_pool_size = market.get_outcome_token_pool_by_outcome(
397
408
  direction
398
409
  )
@@ -403,7 +414,7 @@ class KellyBettingStrategy(BettingStrategy):
403
414
  yes_outcome_pool_size=direction_to_bet_pool_size,
404
415
  no_outcome_pool_size=other_direction_pool_size,
405
416
  estimated_p_yes=estimated_p_yes,
406
- max_bet=market.get_usd_in_token(max_bet_amount),
417
+ max_bet=market.get_usd_in_token(self.max_position_amount),
407
418
  confidence=answer.confidence,
408
419
  fees=market.fees,
409
420
  )
@@ -416,7 +427,7 @@ class KellyBettingStrategy(BettingStrategy):
416
427
  market: AgentMarket,
417
428
  ) -> list[Trade]:
418
429
  # We consider the p_yes as the direction with highest probability.
419
- direction = MultiCategoricalMaxAccuracyBettingStrategy.calculate_direction(
430
+ direction = CategoricalMaxAccuracyBettingStrategy.calculate_direction(
420
431
  market, answer
421
432
  )
422
433
  # We get the first direction which is != direction.
@@ -426,7 +437,6 @@ class KellyBettingStrategy(BettingStrategy):
426
437
 
427
438
  kelly_bet = self.get_kelly_bet(
428
439
  market=market,
429
- max_bet_amount=self.max_position_amount,
430
440
  direction=direction,
431
441
  other_direction=other_direction,
432
442
  answer=answer,
@@ -436,7 +446,10 @@ class KellyBettingStrategy(BettingStrategy):
436
446
  if self.max_price_impact:
437
447
  # Adjust amount
438
448
  max_price_impact_bet_amount = self.calculate_bet_amount_for_price_impact(
439
- market, kelly_bet, direction=direction
449
+ market,
450
+ kelly_bet.size,
451
+ direction=direction,
452
+ max_price_impact=self.max_price_impact,
440
453
  )
441
454
 
442
455
  # We just don't want Kelly size to extrapolate price_impact - hence we take the min.
@@ -447,7 +460,9 @@ class KellyBettingStrategy(BettingStrategy):
447
460
  amounts = {
448
461
  bet_outcome: BettingStrategy.cap_to_profitable_bet_amount(
449
462
  market, market.get_token_in_usd(kelly_bet_size), bet_outcome
450
- ),
463
+ )
464
+ if kelly_bet_size > 0
465
+ else USD(0),
451
466
  }
452
467
  target_position = Position(market_id=market.id, amounts_current=amounts)
453
468
  trades = self._build_rebalance_trades_from_positions(
@@ -455,8 +470,8 @@ class KellyBettingStrategy(BettingStrategy):
455
470
  )
456
471
  return trades
457
472
 
473
+ @staticmethod
458
474
  def calculate_price_impact_for_bet_amount(
459
- self,
460
475
  outcome_idx: int,
461
476
  bet_amount: CollateralToken,
462
477
  pool_balances: list[OutcomeWei],
@@ -474,33 +489,43 @@ class KellyBettingStrategy(BettingStrategy):
474
489
  price_impact = (actual_price - expected_price) / expected_price
475
490
  return price_impact
476
491
 
492
+ @staticmethod
477
493
  def calculate_bet_amount_for_price_impact(
478
- self, market: AgentMarket, kelly_bet: SimpleBet, direction: OutcomeStr
494
+ market: AgentMarket,
495
+ kelly_bet_size: CollateralToken,
496
+ direction: OutcomeStr,
497
+ max_price_impact: float,
479
498
  ) -> CollateralToken:
480
499
  def calculate_price_impact_deviation_from_target_price_impact(
481
500
  bet_amount_collateral: float, # Needs to be float because it's used in minimize_scalar internally.
482
501
  ) -> float:
483
502
  outcome_idx = market.get_outcome_index(direction)
484
- price_impact = self.calculate_price_impact_for_bet_amount(
485
- outcome_idx=outcome_idx,
486
- bet_amount=CollateralToken(bet_amount_collateral),
487
- pool_balances=pool_balances,
488
- fees=market.fees,
503
+ price_impact = (
504
+ BinaryKellyBettingStrategy.calculate_price_impact_for_bet_amount(
505
+ outcome_idx=outcome_idx,
506
+ bet_amount=CollateralToken(bet_amount_collateral),
507
+ pool_balances=pool_balances,
508
+ fees=market.fees,
509
+ )
489
510
  )
490
511
  # We return abs for the algorithm to converge to 0 instead of the min (and possibly negative) value.
491
-
492
- max_price_impact = check_not_none(self.max_price_impact)
493
512
  return abs(price_impact - max_price_impact)
494
513
 
495
514
  if not market.outcome_token_pool:
496
515
  logger.warning(
497
516
  "Market outcome_token_pool is None, cannot calculate bet amount"
498
517
  )
499
- return kelly_bet.size
518
+ return kelly_bet_size
500
519
 
501
- pool_balances = [i.as_outcome_wei for i in market.outcome_token_pool.values()]
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
+
526
+ pool_balances = [i.as_outcome_wei for i in filtered_pool.values()]
502
527
  # stay float for compatibility with `minimize_scalar`
503
- total_pool_balance = sum([i.value for i in market.outcome_token_pool.values()])
528
+ total_pool_balance = sum([i.value for i in filtered_pool.values()])
504
529
 
505
530
  # The bounds below have been found to work heuristically.
506
531
  optimized_bet_amount = minimize_scalar(
@@ -513,7 +538,7 @@ class KellyBettingStrategy(BettingStrategy):
513
538
  return CollateralToken(optimized_bet_amount.x)
514
539
 
515
540
  def __repr__(self) -> str:
516
- return f"{self.__class__.__name__}(max_bet_amount={self.max_position_amount}, max_price_impact={self.max_price_impact})"
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})"
517
542
 
518
543
 
519
544
  class MaxAccuracyWithKellyScaledBetsStrategy(BettingStrategy):
@@ -529,40 +554,29 @@ class MaxAccuracyWithKellyScaledBetsStrategy(BettingStrategy):
529
554
  def maximum_possible_bet_amount(self) -> USD:
530
555
  return self.max_position_amount
531
556
 
532
- def adjust_bet_amount(
533
- self, existing_position: ExistingPosition | None, market: AgentMarket
534
- ) -> USD:
535
- existing_position_total_amount = (
536
- existing_position.total_amount_current if existing_position else USD(0)
537
- )
538
- return self.max_position_amount + existing_position_total_amount
539
-
540
557
  def calculate_trades(
541
558
  self,
542
559
  existing_position: ExistingPosition | None,
543
560
  answer: CategoricalProbabilisticAnswer,
544
561
  market: AgentMarket,
545
562
  ) -> list[Trade]:
546
- adjusted_bet_amount_usd = self.adjust_bet_amount(existing_position, market)
547
-
548
563
  outcome = get_most_probable_outcome(answer.probabilities)
549
564
 
550
- direction = MultiCategoricalMaxAccuracyBettingStrategy.calculate_direction(
565
+ direction = CategoricalMaxAccuracyBettingStrategy.calculate_direction(
551
566
  market, answer
552
567
  )
553
568
  # We get the first direction which is != direction.
554
- other_direction = (
555
- MultiCategoricalMaxAccuracyBettingStrategy.get_other_direction(
556
- outcomes=market.outcomes, direction=direction
557
- )
569
+ other_direction = CategoricalMaxAccuracyBettingStrategy.get_other_direction(
570
+ outcomes=market.outcomes, direction=direction
558
571
  )
559
572
 
560
573
  # We ignore the direction nudge given by Kelly, hence we assume we have a perfect prediction.
561
574
  estimated_p_yes = 1.0
562
575
 
563
- kelly_bet = KellyBettingStrategy.get_kelly_bet(
576
+ kelly_bet = BinaryKellyBettingStrategy(
577
+ max_position_amount=self.max_position_amount
578
+ ).get_kelly_bet(
564
579
  market=market,
565
- max_bet_amount=adjusted_bet_amount_usd,
566
580
  direction=direction,
567
581
  other_direction=other_direction,
568
582
  answer=answer,
@@ -584,4 +598,118 @@ class MaxAccuracyWithKellyScaledBetsStrategy(BettingStrategy):
584
598
  return trades
585
599
 
586
600
  def __repr__(self) -> str:
587
- return f"{self.__class__.__name__}(max_bet_amount={self.max_position_amount})"
601
+ return f"{self.__class__.__name__}(max_position_amount={self.max_position_amount}, take_profit={self.take_profit})"
602
+
603
+
604
+ class CategoricalKellyBettingStrategy(BettingStrategy):
605
+ def __init__(
606
+ self,
607
+ max_position_amount: USD,
608
+ max_price_impact: float | None,
609
+ allow_multiple_bets: bool,
610
+ allow_shorting: bool,
611
+ multicategorical: bool,
612
+ take_profit: bool = True,
613
+ force_simplified_calculation: bool = False,
614
+ ):
615
+ super().__init__(take_profit=take_profit)
616
+ self.max_position_amount = max_position_amount
617
+ self.max_price_impact = max_price_impact
618
+ self.allow_multiple_bets = allow_multiple_bets
619
+ self.allow_shorting = allow_shorting
620
+ self.multicategorical = multicategorical
621
+ self.force_simplified_calculation = force_simplified_calculation
622
+
623
+ @property
624
+ def maximum_possible_bet_amount(self) -> USD:
625
+ return self.max_position_amount
626
+
627
+ def get_kelly_bets(
628
+ self,
629
+ market: AgentMarket,
630
+ max_bet_amount: USD,
631
+ answer: CategoricalProbabilisticAnswer,
632
+ ) -> list[CategoricalKellyBet]:
633
+ max_bet = market.get_usd_in_token(max_bet_amount)
634
+
635
+ if market.outcome_token_pool is None or self.force_simplified_calculation:
636
+ kelly_bets = get_kelly_bets_categorical_simplified(
637
+ market_probabilities=[market.probabilities[o] for o in market.outcomes],
638
+ estimated_probabilities=[
639
+ answer.probability_for_market_outcome(o) for o in market.outcomes
640
+ ],
641
+ confidence=answer.confidence,
642
+ max_bet=max_bet,
643
+ fees=market.fees,
644
+ allow_multiple_bets=self.allow_multiple_bets,
645
+ allow_shorting=self.allow_shorting,
646
+ )
647
+
648
+ else:
649
+ kelly_bets = get_kelly_bets_categorical_full(
650
+ outcome_pool_sizes=[
651
+ market.outcome_token_pool[o] for o in market.outcomes
652
+ ],
653
+ estimated_probabilities=[
654
+ answer.probability_for_market_outcome(o) for o in market.outcomes
655
+ ],
656
+ confidence=answer.confidence,
657
+ max_bet=max_bet,
658
+ fees=market.fees,
659
+ allow_multiple_bets=self.allow_multiple_bets,
660
+ allow_shorting=self.allow_shorting,
661
+ multicategorical=self.multicategorical,
662
+ )
663
+
664
+ return kelly_bets
665
+
666
+ def calculate_trades(
667
+ self,
668
+ existing_position: ExistingPosition | None,
669
+ answer: CategoricalProbabilisticAnswer,
670
+ market: AgentMarket,
671
+ ) -> list[Trade]:
672
+ kelly_bets = self.get_kelly_bets(
673
+ market=market,
674
+ max_bet_amount=self.max_position_amount,
675
+ answer=answer,
676
+ )
677
+
678
+ # TODO: Allow shorting in BettingStrategy._build_rebalance_trades_from_positions.
679
+ # In binary implementation, we simply flip the direction in case of negative bet, for categorical outcome, we need to implement shorting.
680
+ kelly_bets = [bet for bet in kelly_bets if bet.size > 0]
681
+ if not kelly_bets:
682
+ return []
683
+
684
+ # TODO: Allow betting on multiple outcomes.
685
+ # Categorical kelly could suggest to bet on multiple outcomes, but we only consider the first one for now (limitation of BettingStrategy `trades` creation).
686
+ # Also, this could maybe work for multi-categorical markets as well, but it wasn't benchmarked for it.
687
+ best_kelly_bet = max(kelly_bets, key=lambda x: abs(x.size))
688
+
689
+ if self.max_price_impact:
690
+ # Adjust amount
691
+ max_price_impact_bet_amount = (
692
+ BinaryKellyBettingStrategy.calculate_bet_amount_for_price_impact(
693
+ market,
694
+ best_kelly_bet.size,
695
+ direction=market.get_outcome_str(best_kelly_bet.index),
696
+ max_price_impact=self.max_price_impact,
697
+ )
698
+ )
699
+ # We just don't want Kelly size to extrapolate price_impact - hence we take the min.
700
+ best_kelly_bet.size = min(best_kelly_bet.size, max_price_impact_bet_amount)
701
+
702
+ amounts = {
703
+ market.outcomes[best_kelly_bet.index]: market.get_token_in_usd(
704
+ best_kelly_bet.size
705
+ ),
706
+ }
707
+ target_position = Position(market_id=market.id, amounts_current=amounts)
708
+ trades = self._build_rebalance_trades_from_positions(
709
+ existing_position, target_position, market=market
710
+ )
711
+
712
+ return trades
713
+
714
+ 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})"
@@ -2,7 +2,7 @@ import typing as t
2
2
 
3
3
  from prediction_market_agent_tooling.config import APIKeys
4
4
  from prediction_market_agent_tooling.deploy.betting_strategy import (
5
- KellyBettingStrategy,
5
+ BinaryKellyBettingStrategy,
6
6
  TradeType,
7
7
  )
8
8
  from prediction_market_agent_tooling.gtypes import USD
@@ -93,7 +93,7 @@ class OmenJobAgentMarket(OmenAgentMarket, JobAgentMarket):
93
93
 
94
94
  def get_job_trade(self, max_bond: USD, result: str) -> Trade:
95
95
  # 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_position_amount=max_bond)
96
+ strategy = BinaryKellyBettingStrategy(max_position_amount=max_bond)
97
97
  required_trades = strategy.calculate_trades(
98
98
  existing_position=None,
99
99
  answer=self.get_job_answer(result),
@@ -601,7 +601,12 @@ class OmenBet(BaseModel):
601
601
  created_time=self.creation_datetime,
602
602
  market_question=self.title,
603
603
  market_id=self.fpmm.id,
604
- market_outcome=self.fpmm.outcomes[self.outcomeIndex],
604
+ market_outcome=self.fpmm.outcomes[
605
+ check_not_none(
606
+ self.fpmm.answer_index,
607
+ "Should not be None if `is_resolved_with_valid_answer`.",
608
+ )
609
+ ],
605
610
  resolved_time=check_not_none(self.fpmm.finalized_datetime),
606
611
  profit=self.get_profit(),
607
612
  )
@@ -3,6 +3,7 @@ from collections import defaultdict
3
3
  from datetime import timedelta
4
4
 
5
5
  import tenacity
6
+ from pydantic import BaseModel
6
7
  from tqdm import tqdm
7
8
  from web3 import Web3
8
9
 
@@ -1323,20 +1324,34 @@ def send_keeping_token_to_eoa_xdai(
1323
1324
  )
1324
1325
 
1325
1326
 
1326
- def get_buy_outcome_token_amount(
1327
+ class BuyOutcomeResult(BaseModel):
1328
+ outcome_tokens_received: OutcomeToken
1329
+ new_pool_balances: list[OutcomeToken]
1330
+
1331
+
1332
+ def calculate_buy_outcome_token(
1327
1333
  investment_amount: CollateralToken,
1328
1334
  outcome_index: int,
1329
1335
  pool_balances: list[OutcomeToken],
1330
1336
  fees: MarketFees,
1331
- ) -> OutcomeToken:
1337
+ ) -> BuyOutcomeResult:
1332
1338
  """
1333
1339
  Calculates the amount of outcome tokens received for a given investment
1340
+ and returns the new pool balances after the purchase.
1334
1341
 
1335
1342
  Taken from https://github.com/gnosis/conditional-tokens-market-makers/blob/6814c0247c745680bb13298d4f0dd7f5b574d0db/contracts/FixedProductMarketMaker.sol#L264
1336
1343
  """
1337
1344
  if outcome_index >= len(pool_balances):
1338
1345
  raise ValueError("invalid outcome index")
1339
1346
 
1347
+ new_pool_balances = pool_balances.copy()
1348
+
1349
+ if investment_amount == 0:
1350
+ return BuyOutcomeResult(
1351
+ outcome_tokens_received=OutcomeToken(0),
1352
+ new_pool_balances=new_pool_balances,
1353
+ )
1354
+
1340
1355
  investment_amount_minus_fees = fees.get_after_fees(investment_amount)
1341
1356
  investment_amount_minus_fees_as_ot = OutcomeToken(
1342
1357
  investment_amount_minus_fees.value
@@ -1348,17 +1363,39 @@ def get_buy_outcome_token_amount(
1348
1363
  # Calculate the ending balance considering all other outcomes
1349
1364
  for i, pool_balance in enumerate(pool_balances):
1350
1365
  if i != outcome_index:
1351
- denominator = pool_balance + investment_amount_minus_fees_as_ot
1366
+ new_pool_balances[i] = pool_balance + investment_amount_minus_fees_as_ot
1352
1367
  ending_outcome_balance = OutcomeToken(
1353
- (ending_outcome_balance * pool_balance / denominator)
1368
+ (ending_outcome_balance * pool_balance / new_pool_balances[i])
1354
1369
  )
1355
1370
 
1371
+ # Update the bought outcome's pool balance
1372
+ new_pool_balances[outcome_index] = ending_outcome_balance
1373
+
1356
1374
  if ending_outcome_balance <= 0:
1357
1375
  raise ValueError("must have non-zero balances")
1358
1376
 
1359
- result = (
1377
+ outcome_tokens_received = (
1360
1378
  buy_token_pool_balance
1361
1379
  + investment_amount_minus_fees_as_ot
1362
1380
  - ending_outcome_balance
1363
1381
  )
1364
- return result
1382
+
1383
+ return BuyOutcomeResult(
1384
+ outcome_tokens_received=outcome_tokens_received,
1385
+ new_pool_balances=new_pool_balances,
1386
+ )
1387
+
1388
+
1389
+ def get_buy_outcome_token_amount(
1390
+ investment_amount: CollateralToken,
1391
+ outcome_index: int,
1392
+ pool_balances: list[OutcomeToken],
1393
+ fees: MarketFees,
1394
+ ) -> OutcomeToken:
1395
+ result = calculate_buy_outcome_token(
1396
+ investment_amount=investment_amount,
1397
+ outcome_index=outcome_index,
1398
+ pool_balances=pool_balances,
1399
+ fees=fees,
1400
+ )
1401
+ return result.outcome_tokens_received
@@ -2,11 +2,15 @@ from cachetools import TTLCache, cached
2
2
  from pydantic import BaseModel
3
3
  from web3 import Web3
4
4
 
5
+ from prediction_market_agent_tooling.deploy.constants import (
6
+ INVALID_OUTCOME_LOWERCASE_IDENTIFIER,
7
+ )
5
8
  from prediction_market_agent_tooling.gtypes import (
6
9
  ChecksumAddress,
7
10
  CollateralToken,
8
11
  HexAddress,
9
12
  OutcomeStr,
13
+ OutcomeToken,
10
14
  Probability,
11
15
  )
12
16
  from prediction_market_agent_tooling.loggers import logger
@@ -182,3 +186,67 @@ class PriceManager:
182
186
  normalized_prices[outcome] = new_price
183
187
 
184
188
  return normalized_prices
189
+
190
+ def build_initial_probs_from_pool(
191
+ self, model: SeerMarket, wrapped_tokens: list[ChecksumAddress]
192
+ ) -> tuple[dict[OutcomeStr, Probability], dict[OutcomeStr, OutcomeToken]]:
193
+ """
194
+ Builds a map of outcome to probability and outcome token pool.
195
+ """
196
+ probability_map = {}
197
+ outcome_token_pool = {}
198
+ wrapped_tokens_with_supply = [
199
+ (
200
+ token,
201
+ SeerSubgraphHandler().get_pool_by_token(
202
+ token, model.collateral_token_contract_address_checksummed
203
+ ),
204
+ )
205
+ for token in wrapped_tokens
206
+ ]
207
+ wrapped_tokens_with_supply = [
208
+ (token, pool)
209
+ for token, pool in wrapped_tokens_with_supply
210
+ if pool is not None
211
+ ]
212
+
213
+ for token, pool in wrapped_tokens_with_supply:
214
+ if pool is None or pool.token1.id is None or pool.token0.id is None:
215
+ continue
216
+ if HexBytes(token) == HexBytes(pool.token1.id):
217
+ outcome_token_pool[
218
+ OutcomeStr(model.outcomes[wrapped_tokens.index(token)])
219
+ ] = (
220
+ OutcomeToken(pool.totalValueLockedToken0)
221
+ if pool.totalValueLockedToken0 is not None
222
+ else OutcomeToken(0)
223
+ )
224
+ probability_map[
225
+ OutcomeStr(model.outcomes[wrapped_tokens.index(token)])
226
+ ] = Probability(pool.token0Price.value)
227
+ else:
228
+ outcome_token_pool[
229
+ OutcomeStr(model.outcomes[wrapped_tokens.index(token)])
230
+ ] = (
231
+ OutcomeToken(pool.totalValueLockedToken1)
232
+ if pool.totalValueLockedToken1 is not None
233
+ else OutcomeToken(0)
234
+ )
235
+ probability_map[
236
+ OutcomeStr(model.outcomes[wrapped_tokens.index(token)])
237
+ ] = Probability(pool.token1Price.value)
238
+
239
+ for outcome in model.outcomes:
240
+ if outcome not in outcome_token_pool:
241
+ outcome_token_pool[outcome] = OutcomeToken(0)
242
+ logger.warning(
243
+ f"Outcome {outcome} not found in outcome_token_pool for market {self.seer_market.url}."
244
+ )
245
+ if outcome not in probability_map:
246
+ if INVALID_OUTCOME_LOWERCASE_IDENTIFIER not in outcome.lower():
247
+ raise PriceCalculationError(
248
+ f"Couldn't get probability for {outcome} for market {self.seer_market.url}."
249
+ )
250
+ else:
251
+ probability_map[outcome] = Probability(0)
252
+ return probability_map, outcome_token_pool
@@ -411,10 +411,14 @@ class SeerAgentMarket(AgentMarket):
411
411
  must_have_prices: bool,
412
412
  ) -> t.Optional["SeerAgentMarket"]:
413
413
  price_manager = PriceManager(seer_market=model, seer_subgraph=seer_subgraph)
414
-
415
- probability_map = {}
414
+ wrapped_tokens = [Web3.to_checksum_address(i) for i in model.wrapped_tokens]
416
415
  try:
417
- probability_map = price_manager.build_probability_map()
416
+ (
417
+ probability_map,
418
+ outcome_token_pool,
419
+ ) = price_manager.build_initial_probs_from_pool(
420
+ model=model, wrapped_tokens=wrapped_tokens
421
+ )
418
422
  except PriceCalculationError as e:
419
423
  logger.info(
420
424
  f"Error when calculating probabilities for market {model.id.hex()} - {e}"
@@ -437,9 +441,9 @@ class SeerAgentMarket(AgentMarket):
437
441
  condition_id=model.condition_id,
438
442
  url=model.url,
439
443
  close_time=model.close_time,
440
- wrapped_tokens=[Web3.to_checksum_address(i) for i in model.wrapped_tokens],
444
+ wrapped_tokens=wrapped_tokens,
441
445
  fees=MarketFees.get_zero_fees(),
442
- outcome_token_pool=None,
446
+ outcome_token_pool=outcome_token_pool,
443
447
  outcomes_supply=model.outcomes_supply,
444
448
  resolution=resolution,
445
449
  volume=None,
@@ -633,9 +637,9 @@ class SeerAgentMarket(AgentMarket):
633
637
  amount_wei=amount_wei,
634
638
  web3=web3,
635
639
  )
636
- swap_pool_tx_hash = tx_receipt["transactionHash"].hex()
640
+ swap_pool_tx_hash = tx_receipt["transactionHash"]
637
641
  logger.info(f"TxHash is {swap_pool_tx_hash=}.")
638
- return swap_pool_tx_hash
642
+ return swap_pool_tx_hash.hex()
639
643
 
640
644
  def place_bet(
641
645
  self,
@@ -352,6 +352,8 @@ class SeerSubgraphHandler(BaseSubgraphHandler):
352
352
  pools_field.token1.id,
353
353
  pools_field.token1.name,
354
354
  pools_field.token1.symbol,
355
+ pools_field.totalValueLockedToken0,
356
+ pools_field.totalValueLockedToken1,
355
357
  ]
356
358
  return fields
357
359
 
@@ -24,6 +24,8 @@ class SeerPool(BaseModel):
24
24
  token0Price: CollateralToken
25
25
  token1Price: CollateralToken
26
26
  sqrtPrice: int
27
+ totalValueLockedToken0: float
28
+ totalValueLockedToken1: float
27
29
 
28
30
 
29
31
  class NewMarketEvent(BaseModel):
@@ -1,6 +1,24 @@
1
- from prediction_market_agent_tooling.gtypes import CollateralToken, OutcomeToken
1
+ from itertools import chain
2
+
3
+ import numpy as np
4
+ from scipy.optimize import minimize
5
+
6
+ from prediction_market_agent_tooling.gtypes import (
7
+ CollateralToken,
8
+ OutcomeToken,
9
+ Probability,
10
+ )
11
+ from prediction_market_agent_tooling.loggers import logger
12
+ from prediction_market_agent_tooling.markets.agent_market import AgentMarket
2
13
  from prediction_market_agent_tooling.markets.market_fees import MarketFees
3
- from prediction_market_agent_tooling.tools.betting_strategies.utils import SimpleBet
14
+ from prediction_market_agent_tooling.markets.omen.omen import (
15
+ calculate_buy_outcome_token,
16
+ )
17
+ from prediction_market_agent_tooling.tools.betting_strategies.utils import (
18
+ BinaryKellyBet,
19
+ CategoricalKellyBet,
20
+ )
21
+ from prediction_market_agent_tooling.tools.utils import check_not_none
4
22
 
5
23
 
6
24
  def check_is_valid_probability(probability: float) -> None:
@@ -13,7 +31,7 @@ def get_kelly_bet_simplified(
13
31
  market_p_yes: float,
14
32
  estimated_p_yes: float,
15
33
  confidence: float,
16
- ) -> SimpleBet:
34
+ ) -> BinaryKellyBet:
17
35
  """
18
36
  Calculate the optimal bet amount using the Kelly Criterion for a binary outcome market.
19
37
 
@@ -39,22 +57,24 @@ def get_kelly_bet_simplified(
39
57
  if estimated_p_yes > market_p_yes:
40
58
  bet_direction = True
41
59
  market_prob = market_p_yes
60
+ estimated_p = estimated_p_yes
42
61
  else:
43
62
  bet_direction = False
44
63
  market_prob = 1 - market_p_yes
64
+ estimated_p = 1 - estimated_p_yes
45
65
 
46
66
  # Handle the case where market_prob is 0
47
67
  if market_prob == 0:
48
68
  market_prob = 1e-10
49
69
 
50
- edge = abs(estimated_p_yes - market_p_yes) * confidence
70
+ edge = abs(estimated_p - market_prob) * confidence
51
71
  odds = (1 / market_prob) - 1
52
72
  kelly_fraction = edge / odds
53
73
 
54
74
  # Ensure bet size is non-negative does not exceed the wallet balance
55
75
  bet_size = CollateralToken(min(kelly_fraction * max_bet.value, max_bet.value))
56
76
 
57
- return SimpleBet(direction=bet_direction, size=bet_size)
77
+ return BinaryKellyBet(direction=bet_direction, size=bet_size)
58
78
 
59
79
 
60
80
  def get_kelly_bet_full(
@@ -64,7 +84,7 @@ def get_kelly_bet_full(
64
84
  confidence: float,
65
85
  max_bet: CollateralToken,
66
86
  fees: MarketFees,
67
- ) -> SimpleBet:
87
+ ) -> BinaryKellyBet:
68
88
  """
69
89
  Calculate the optimal bet amount using the Kelly Criterion for a binary outcome market.
70
90
 
@@ -98,7 +118,7 @@ def get_kelly_bet_full(
98
118
  check_is_valid_probability(confidence)
99
119
 
100
120
  if max_bet == 0:
101
- return SimpleBet(size=CollateralToken(0), direction=True)
121
+ return BinaryKellyBet(size=CollateralToken(0), direction=True)
102
122
 
103
123
  x = yes_outcome_pool_size.value
104
124
  y = no_outcome_pool_size.value
@@ -144,7 +164,255 @@ def get_kelly_bet_full(
144
164
  kelly_bet_amount = numerator / denominator
145
165
 
146
166
  # Clip the bet size to max_bet to account for rounding errors.
147
- return SimpleBet(
167
+ return BinaryKellyBet(
148
168
  direction=kelly_bet_amount > 0,
149
169
  size=CollateralToken(min(max_bet.value, abs(kelly_bet_amount))),
150
170
  )
171
+
172
+
173
+ def get_kelly_bets_categorical_simplified(
174
+ market_probabilities: list[Probability],
175
+ estimated_probabilities: list[Probability],
176
+ confidence: float,
177
+ max_bet: CollateralToken,
178
+ fees: MarketFees,
179
+ allow_multiple_bets: bool,
180
+ allow_shorting: bool,
181
+ bet_precision: int = 6,
182
+ ) -> list[CategoricalKellyBet]:
183
+ """
184
+ Calculate Kelly bets for categorical markets using only market probabilities.
185
+ Returns a list of CategoricalKellyBet objects, one for each outcome.
186
+ Considers max_bet across all outcomes together.
187
+ Indicates both buying (long) and shorting (selling) by allowing negative bet sizes.
188
+ """
189
+ for p in chain(market_probabilities, estimated_probabilities, [confidence]):
190
+ check_is_valid_probability(p)
191
+ assert len(market_probabilities) == len(
192
+ estimated_probabilities
193
+ ), "Mismatch in number of outcomes"
194
+
195
+ f = 1 - fees.bet_proportion
196
+
197
+ total_kelly_fraction = 0.0
198
+ kelly_fractions = []
199
+
200
+ for i in range(len(market_probabilities)):
201
+ estimated_p = estimated_probabilities[i]
202
+ market_p = max(market_probabilities[i], 1e-10)
203
+
204
+ edge = (estimated_p - market_p) * confidence
205
+ odds = (1 / market_p) - 1
206
+ kelly_fraction = edge / odds * f
207
+
208
+ if not allow_shorting:
209
+ kelly_fraction = max(0, kelly_fraction)
210
+
211
+ kelly_fractions.append(kelly_fraction)
212
+ total_kelly_fraction += abs(kelly_fraction)
213
+
214
+ best_kelly_fraction_index = max(
215
+ range(len(kelly_fractions)), key=lambda i: abs(kelly_fractions[i])
216
+ )
217
+
218
+ bets = []
219
+ for i, kelly_fraction in enumerate(kelly_fractions):
220
+ if not allow_multiple_bets:
221
+ bet_size = (
222
+ kelly_fraction * max_bet.value if i == best_kelly_fraction_index else 0
223
+ )
224
+ elif allow_multiple_bets and total_kelly_fraction > 0:
225
+ bet_size = (kelly_fraction / total_kelly_fraction) * max_bet.value
226
+ else:
227
+ bet_size = 0.0
228
+ # Ensure bet_size is within [-max_bet.value, max_bet.value]
229
+ bet_size = max(-max_bet.value, min(bet_size, max_bet.value))
230
+ bets.append(
231
+ CategoricalKellyBet(
232
+ index=i, size=CollateralToken(round(bet_size, bet_precision))
233
+ )
234
+ )
235
+
236
+ return bets
237
+
238
+
239
+ def get_kelly_bets_categorical_full(
240
+ outcome_pool_sizes: list[OutcomeToken],
241
+ estimated_probabilities: list[Probability],
242
+ confidence: float,
243
+ max_bet: CollateralToken,
244
+ fees: MarketFees,
245
+ allow_multiple_bets: bool,
246
+ allow_shorting: bool,
247
+ multicategorical: bool,
248
+ bet_precision: int = 6,
249
+ ) -> list[CategoricalKellyBet]:
250
+ """
251
+ Calculate Kelly bets for categorical markets using joint optimization over all outcomes,
252
+ splitting the max bet between all possible outcomes to maximize expected log utility.
253
+ Returns a list of CategoricalKellyBet objects, one for each outcome.
254
+ Handles both buying (long) and shorting (selling) by allowing negative bet sizes.
255
+ If the agent's probabilities are very close to the market's, returns all-zero bets.
256
+ multicategorical means that multiple outcomes could be selected as correct ones.
257
+ """
258
+ assert len(outcome_pool_sizes) == len(
259
+ estimated_probabilities
260
+ ), "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
+ for p in chain(market_probabilities, estimated_probabilities, [confidence]):
267
+ check_is_valid_probability(p)
268
+
269
+ n = len(outcome_pool_sizes)
270
+ max_bet_value = max_bet.value
271
+
272
+ if all(
273
+ abs(estimated_probabilities[i] - market_probabilities[i]) < 1e-3
274
+ for i in range(n)
275
+ ):
276
+ return [
277
+ CategoricalKellyBet(index=i, size=CollateralToken(0.0)) for i in range(n)
278
+ ]
279
+
280
+ def compute_payouts(bets: list[float]) -> list[float]:
281
+ payouts: list[float] = []
282
+ for i in range(n):
283
+ payout = 0.0
284
+ if bets[i] >= 0:
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
290
+ else:
291
+ # If bet is negative, we "short" outcome i by buying all other outcomes
292
+ for j in range(n):
293
+ if j == i:
294
+ continue
295
+ buy_result = calculate_buy_outcome_token(
296
+ CollateralToken(abs(bets[i]) / (n - 1)),
297
+ j,
298
+ outcome_pool_sizes,
299
+ fees,
300
+ )
301
+ payout += buy_result.outcome_tokens_received.value
302
+ payouts.append(payout)
303
+ return payouts
304
+
305
+ def adjust_prob(my_prob: float, market_prob: float) -> float:
306
+ # Based on the confidence, shrinks the predicted probability towards market's current probability.
307
+ return confidence * my_prob + (1 - confidence) * market_prob
308
+
309
+ # Use the simple version to estimate the initial bet vector.
310
+ x0 = np.array(
311
+ [
312
+ x.size.value # Use simplified value as starting point
313
+ for x in get_kelly_bets_categorical_simplified(
314
+ market_probabilities=market_probabilities,
315
+ estimated_probabilities=estimated_probabilities,
316
+ confidence=confidence,
317
+ max_bet=max_bet,
318
+ fees=fees,
319
+ allow_multiple_bets=allow_multiple_bets,
320
+ allow_shorting=allow_shorting,
321
+ bet_precision=bet_precision,
322
+ )
323
+ ]
324
+ )
325
+
326
+ # Track the best solution found during optimization
327
+ best_solution_bets = None
328
+ best_solution_utility = float("-inf")
329
+
330
+ def neg_expected_log_utility(bets: list[float]) -> float:
331
+ """
332
+ Negative expected log utility for categorical Kelly betting.
333
+ This function is minimized to find the optimal bet allocation.
334
+ """
335
+ adj_probs = [
336
+ adjust_prob(estimated_probabilities[i], market_probabilities[i])
337
+ for i in range(n)
338
+ ]
339
+ payouts = compute_payouts(bets)
340
+
341
+ profits = [payout - abs(bet) for payout, bet in zip(payouts, bets)]
342
+
343
+ # Ensure profits are not too negative to avoid log(negative) or log(0)
344
+ # Use a small epsilon to prevent numerical instability
345
+ min_profit = -0.99 # Ensure 1 + profit > 0.01
346
+ profits = [max(profit, min_profit) for profit in profits]
347
+
348
+ # Expected log utility
349
+ expected_log_utility: float = sum(
350
+ adj_probs[i] * np.log(1 + profits[i]) for i in range(n)
351
+ )
352
+
353
+ # Track the best solution found so far
354
+ nonlocal best_solution_bets, best_solution_utility
355
+ if expected_log_utility > best_solution_utility:
356
+ best_solution_bets = np.array(bets)
357
+ best_solution_utility = expected_log_utility
358
+
359
+ # Return negative for minimization
360
+ return -expected_log_utility
361
+
362
+ constraints = [
363
+ # We can not bet more than `max_bet_value`
364
+ {
365
+ "type": "ineq",
366
+ "fun": lambda bets: max_bet_value - np.sum(np.abs(bets)),
367
+ },
368
+ # Each bet should not result in guaranteed loss
369
+ {
370
+ "type": "ineq",
371
+ "fun": lambda bets: [
372
+ payout
373
+ - (sum(abs(b) for b in bets) if not multicategorical else abs(bets[i]))
374
+ for i, payout in enumerate(compute_payouts(bets))
375
+ ],
376
+ },
377
+ ]
378
+
379
+ result = minimize(
380
+ neg_expected_log_utility,
381
+ x0,
382
+ method="SLSQP",
383
+ bounds=[
384
+ ((-max_bet_value if allow_shorting else 0), max_bet_value) for _ in range(n)
385
+ ],
386
+ constraints=constraints,
387
+ options={"maxiter": 10_000},
388
+ )
389
+
390
+ # This can sometimes happen, as long as it's occasional, it's should be fine to just use simplified version approximation.
391
+ if not result.success:
392
+ logger.warning(
393
+ f"Joint optimization failed: {result=} {x0=} {estimated_probabilities=} {confidence=} {market_probabilities=}"
394
+ )
395
+
396
+ # Use the best solution found during optimization, not just the final result (result.x).
397
+ # This is important because SLSQP may end on a worse solution due to numerical issues.
398
+ bet_vector = check_not_none(best_solution_bets) if result.success else x0
399
+
400
+ if not allow_multiple_bets:
401
+ # If we are not allowing multiple bets, we need to ensure only one bet is non-zero.
402
+ # We can do this by taking the maximum bet and setting all others to zero.
403
+ # We do this, instead of enforcing it in with additional constraint,
404
+ # because such hard constraint is problematic for the solver and results in almost always failing to optimize.
405
+ max_bet_index = np.argmax(np.abs(bet_vector))
406
+ max_bet_value = bet_vector[max_bet_index]
407
+
408
+ bet_vector = np.zeros_like(bet_vector)
409
+ bet_vector[max_bet_index] = max_bet_value
410
+
411
+ bets = [
412
+ CategoricalKellyBet(
413
+ index=i, size=CollateralToken(round(bet_vector[i], bet_precision))
414
+ )
415
+ for i in range(n)
416
+ ]
417
+
418
+ return bets
@@ -3,6 +3,11 @@ from pydantic import BaseModel
3
3
  from prediction_market_agent_tooling.gtypes import CollateralToken
4
4
 
5
5
 
6
- class SimpleBet(BaseModel):
6
+ class BinaryKellyBet(BaseModel):
7
7
  direction: bool
8
8
  size: CollateralToken
9
+
10
+
11
+ class CategoricalKellyBet(BaseModel):
12
+ index: int
13
+ size: CollateralToken
@@ -159,9 +159,6 @@ def get_trace_for_bet(
159
159
  not in WRAPPED_XDAI_CONTRACT_ADDRESS
160
160
  ):
161
161
  # TODO: We need to compute bet amount token in USD here, but at the time of bet placement!
162
- logger.warning(
163
- "This currently works only for WXDAI markets, because we need to compare against USD value."
164
- )
165
162
  continue
166
163
  # Cannot use exact comparison due to gas fees
167
164
  if (
@@ -9,6 +9,7 @@ from eth_typing import URI
9
9
  from eth_utils.currency import MAX_WEI, MIN_WEI
10
10
  from pydantic.types import SecretStr
11
11
  from safe_eth.eth import EthereumClient
12
+ from safe_eth.eth.ethereum_client import TxSpeed
12
13
  from safe_eth.safe.safe import SafeV141
13
14
  from web3 import Web3
14
15
  from web3.constants import HASH_ZERO
@@ -269,6 +270,7 @@ def send_function_on_contract_tx_using_safe(
269
270
  tx_hash, tx = safe_tx.execute(
270
271
  from_private_key.get_secret_value(),
271
272
  tx_nonce=eoa_nonce,
273
+ eip1559_speed=TxSpeed.FAST,
272
274
  )
273
275
  receipt_tx = web3.eth.wait_for_transaction_receipt(tx_hash, timeout=timeout)
274
276
  check_tx_receipt(receipt_tx)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: prediction-market-agent-tooling
3
- Version: 0.67.4
3
+ Version: 0.68.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=1qQstoqXMwqwM7k-KH7MjMz8Ei-D83KZByvDbCZpAxs,116
28
28
  prediction_market_agent_tooling/config.py,sha256=-kJfdDr-m0R-tGZ1KRI-hJJk0mXDt142CAlvwaJ2N2I,11778
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=eFiOaBgaEcUuxUNnqRdXrWen3lEeyhzM9EzLY4NY07Q,30380
30
+ prediction_market_agent_tooling/deploy/agent.py,sha256=N0lbYOfxv3QsmfyteT_xisEg8jEwFujUe1ZsM6WvtTY,30348
31
31
  prediction_market_agent_tooling/deploy/agent_example.py,sha256=yS1fWkHynr9MYGNOM2WsCnRWLPaffY4bOc6bIudrdd4,1377
32
- prediction_market_agent_tooling/deploy/betting_strategy.py,sha256=N3asdTTNA1i72Fpob1C7rffwDoV-ghnjrojPBxYq4Cw,21248
32
+ prediction_market_agent_tooling/deploy/betting_strategy.py,sha256=E_0P8MFBtm50OT9BCAy_ZSHW80skDPtSr-vgGZqNjDs,26584
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,7 +38,7 @@ 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=DoZ9dlvVhpNrnINiR1uy6YUOsuzI_L-avBt362y5xXM,2467
41
- prediction_market_agent_tooling/jobs/omen/omen_jobs.py,sha256=lCymxn0iH4xDmqouTP2LMORoGCiTzlK1_yqYtx1Njj4,5132
41
+ prediction_market_agent_tooling/jobs/omen/omen_jobs.py,sha256=7hCy0iJD9oDmfZVlsDqOc4yCwDhUxvzjgtJzkbvFHu4,5144
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
44
  prediction_market_agent_tooling/markets/agent_market.py,sha256=AvIJdeu4LAMtxAvQIRZktrbUYOczb3kditVVUGyF5EU,21289
@@ -58,8 +58,8 @@ prediction_market_agent_tooling/markets/metaculus/data_models.py,sha256=WjPt0MKe
58
58
  prediction_market_agent_tooling/markets/metaculus/metaculus.py,sha256=kM0U0k90YfLE82ra53JAoCR9uIc9wm4B8l32hLw5imU,5312
59
59
  prediction_market_agent_tooling/markets/omen/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
60
60
  prediction_market_agent_tooling/markets/omen/cow_contracts.py,sha256=sFaW82u_haL4nze8fjTmnQsOuV0OecunQlAhh1OAw0w,1091
61
- prediction_market_agent_tooling/markets/omen/data_models.py,sha256=RsBYSbM4deA6Os4kQ3egH3HvwT80tQho6T1yyoATCMs,31103
62
- prediction_market_agent_tooling/markets/omen/omen.py,sha256=9ynjn_UL48ach_GQt-TdcrY4ut1gCObkRCb8adePtHA,50700
61
+ prediction_market_agent_tooling/markets/omen/data_models.py,sha256=K8BmRVAg4HBvtbrTGukq3OopegFQZKg8aD6eS5X9i4A,31271
62
+ prediction_market_agent_tooling/markets/omen/omen.py,sha256=5FJTpeY8LiIkIZQN1hcHGMVugrdycWW-3iIBjQqFnSY,51809
63
63
  prediction_market_agent_tooling/markets/omen/omen_constants.py,sha256=XtRk4vpxwUYkTndfjlcmghA-NOIneV8zdHFdyI7tHhM,487
64
64
  prediction_market_agent_tooling/markets/omen/omen_contracts.py,sha256=27-HRngTqfk_wgvttB3GeVHhy_O2YZcz9izo9OufOI0,29991
65
65
  prediction_market_agent_tooling/markets/omen/omen_resolving.py,sha256=D-ubf_LumHs_c5rBAAntQ8wGKprtO2V1JZeedmChNIE,11035
@@ -72,18 +72,18 @@ prediction_market_agent_tooling/markets/polymarket/polymarket_subgraph_handler.p
72
72
  prediction_market_agent_tooling/markets/polymarket/utils.py,sha256=A_diygWKYyp4WHbxAlAVAuC0S0ZqbEKE80wxL6mxHKQ,1275
73
73
  prediction_market_agent_tooling/markets/seer/data_models.py,sha256=d8J-hgUmGGhhv41u816O7xvBsi9m-TginULvj82hf_g,6681
74
74
  prediction_market_agent_tooling/markets/seer/exceptions.py,sha256=cEObdjluivD94tgOLzmimR7wgQEOt6SRakrYdhsRQtk,112
75
- prediction_market_agent_tooling/markets/seer/price_manager.py,sha256=PZf6-6zc6DvH1u65wHWyeD55lgG-UGnN_xzBMvrb3ug,7120
76
- prediction_market_agent_tooling/markets/seer/seer.py,sha256=i_a7VtAogk3NvLWCgLjF9EqK-C1AChuEaDpl06fsJYc,28861
75
+ prediction_market_agent_tooling/markets/seer/price_manager.py,sha256=kL9yNM-4JDmQ3MHGJQCGC508gVNsf89JkajjUXj_sko,10001
76
+ prediction_market_agent_tooling/markets/seer/seer.py,sha256=6oiPoa8qd6WF4_G0uVckS97gmC0YP5kv-iiw0vKna_s,29034
77
77
  prediction_market_agent_tooling/markets/seer/seer_contracts.py,sha256=JqfQNFSRWREPw6pQGpJoh-No5ZlKwmTiALJiAYEuvW8,5516
78
- prediction_market_agent_tooling/markets/seer/seer_subgraph_handler.py,sha256=Ta4wb-P42Q0_GJfAtHkTjRUlknxwgHmjwdUqrgfHi0s,17133
79
- prediction_market_agent_tooling/markets/seer/subgraph_data_models.py,sha256=7szvK5we3LF38UthWHg5V3LD6C137O5_WMEcP9Dwl8w,1763
78
+ prediction_market_agent_tooling/markets/seer/seer_subgraph_handler.py,sha256=9a1LAoSDg2mUE4-U0rq2KUSRVAn4A0GbP8b0fr5Mmbg,17229
79
+ prediction_market_agent_tooling/markets/seer/subgraph_data_models.py,sha256=VREm8bR-gPY06ODFJsaiSMinYgN64q5dC6DFckMY6oA,1831
80
80
  prediction_market_agent_tooling/markets/seer/swap_pool_handler.py,sha256=k_sCEJZLroVDjOVkZ084VKJGNODLGjBGezhsWEZvlH4,3528
81
81
  prediction_market_agent_tooling/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
82
82
  prediction_market_agent_tooling/tools/_generic_value.py,sha256=pD_PI13lpPp1gFoljHwa_Lzlp-u2pu0m-Z7LcxwDM2U,10618
83
83
  prediction_market_agent_tooling/tools/balances.py,sha256=Osab21btfJDw2Y-jT_TV-KHGrseCRxcsYeW6WcOMB8E,1050
84
- prediction_market_agent_tooling/tools/betting_strategies/kelly_criterion.py,sha256=duylr4I06L0-Vpeomatzt3_dxs6bijt41TJPwm1YjRg,4873
84
+ prediction_market_agent_tooling/tools/betting_strategies/kelly_criterion.py,sha256=C1rWbj88gPsBYVpKSAoJgZ1iVfOL2YJ5tXnvqIxmMKk,14716
85
85
  prediction_market_agent_tooling/tools/betting_strategies/stretch_bet_between.py,sha256=THMXwFlskvzbjnX_OiYtDSzI8XVFyULWfP2525_9UGc,429
86
- prediction_market_agent_tooling/tools/betting_strategies/utils.py,sha256=68zFWUj43GUaSpOPowVrbI-t6qbCE29RsVHNzCVuJ9U,175
86
+ prediction_market_agent_tooling/tools/betting_strategies/utils.py,sha256=MpS3FOMn0C7nbmbQRUT9QwSh3UzzsgGczP91iSMr9wo,261
87
87
  prediction_market_agent_tooling/tools/caches/db_cache.py,sha256=rZIGhgijquwwPtp_qncSAPR1SDF2XxIVZL1ir0fgzWw,12127
88
88
  prediction_market_agent_tooling/tools/caches/inmemory_cache.py,sha256=ZW5iI5rmjqeAebu5T7ftRnlkxiL02IC-MxCfDB80x7w,1506
89
89
  prediction_market_agent_tooling/tools/caches/serializers.py,sha256=vFDx4fsPxclXp2q0sv27j4al_M_Tj9aR2JJP-xNHQXA,2151
@@ -104,7 +104,7 @@ prediction_market_agent_tooling/tools/ipfs/ipfs_handler.py,sha256=CTTMfTvs_8PH4k
104
104
  prediction_market_agent_tooling/tools/is_invalid.py,sha256=ArYgPqZApRwIPXwBmTdz7d-7ynP856GLaeCcIl9tV08,5334
105
105
  prediction_market_agent_tooling/tools/is_predictable.py,sha256=RNwVH31qpHtg2UWdeMZoFOpMsKgE-aKmcc3Uj6_dK-A,6909
106
106
  prediction_market_agent_tooling/tools/langfuse_.py,sha256=jI_4ROxqo41CCnWGS1vN_AeDVhRzLMaQLxH3kxDu3L8,1153
107
- prediction_market_agent_tooling/tools/langfuse_client_utils.py,sha256=7cyGcin2-fhhp_wxAGA_uzpEUUbI8lTud3MJndjYnoM,6795
107
+ prediction_market_agent_tooling/tools/langfuse_client_utils.py,sha256=D_AzGvEg-YXc0xhYGALGPnmtQqeNyNxkP0SWMCRQW3M,6644
108
108
  prediction_market_agent_tooling/tools/omen/reality_accuracy.py,sha256=M1SF7iSW1gVlQSTskdVFTn09uPLST23YeipVIWj54io,2236
109
109
  prediction_market_agent_tooling/tools/omen/sell_positions.py,sha256=Q4oI7_QI3AkyxlH10VvxDahYVrphQa1Wnox2Ce_cf_k,2452
110
110
  prediction_market_agent_tooling/tools/parallelism.py,sha256=6Gou0hbjtMZrYvxjTDFUDZuxmE2nqZVbb6hkg1hF82A,1022
@@ -128,9 +128,9 @@ prediction_market_agent_tooling/tools/tokens/token_utils.py,sha256=fhs-FH9m9IbzG
128
128
  prediction_market_agent_tooling/tools/tokens/usd.py,sha256=yuW8iPPtcpP4eLH2nORMDAfztcq0Nv2ascSrCquF1f8,3115
129
129
  prediction_market_agent_tooling/tools/transaction_cache.py,sha256=K5YKNL2_tR10Iw2TD9fuP-CTGpBbZtNdgbd0B_R7pjg,1814
130
130
  prediction_market_agent_tooling/tools/utils.py,sha256=mbOGoWKalNIm7M2K51TEPGwU9oVp1Z6SPsFaBgbn6ws,7397
131
- prediction_market_agent_tooling/tools/web3_utils.py,sha256=0r26snqCXGdLKCWA8jpe7DV8x2NPYWZwOy4oyKyDCYk,12615
132
- prediction_market_agent_tooling-0.67.4.dist-info/LICENSE,sha256=6or154nLLU6bELzjh0mCreFjt0m2v72zLi3yHE0QbeE,7650
133
- prediction_market_agent_tooling-0.67.4.dist-info/METADATA,sha256=jEFepq9cqrzVIqjTyZODBnvEJ9jJGREtzIqKd05YrLE,8770
134
- prediction_market_agent_tooling-0.67.4.dist-info/WHEEL,sha256=b4K_helf-jlQoXBBETfwnf4B04YC67LOev0jo4fX5m8,88
135
- prediction_market_agent_tooling-0.67.4.dist-info/entry_points.txt,sha256=m8PukHbeH5g0IAAmOf_1Ahm-sGAMdhSSRQmwtpmi2s8,81
136
- prediction_market_agent_tooling-0.67.4.dist-info/RECORD,,
131
+ prediction_market_agent_tooling/tools/web3_utils.py,sha256=dBRlDcq532fkq6mRrvDlafx8u0pKdbgkfbDdCMGqhR4,12700
132
+ prediction_market_agent_tooling-0.68.0.dist-info/LICENSE,sha256=6or154nLLU6bELzjh0mCreFjt0m2v72zLi3yHE0QbeE,7650
133
+ prediction_market_agent_tooling-0.68.0.dist-info/METADATA,sha256=ic8wMeKZRZRaQPKHVW4wkzZ0FKIqBMjYqGHTVRCdcuE,8770
134
+ prediction_market_agent_tooling-0.68.0.dist-info/WHEEL,sha256=b4K_helf-jlQoXBBETfwnf4B04YC67LOev0jo4fX5m8,88
135
+ prediction_market_agent_tooling-0.68.0.dist-info/entry_points.txt,sha256=m8PukHbeH5g0IAAmOf_1Ahm-sGAMdhSSRQmwtpmi2s8,81
136
+ prediction_market_agent_tooling-0.68.0.dist-info/RECORD,,