prediction-market-agent-tooling 0.67.4__py3-none-any.whl → 0.67.4.dev992__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.
@@ -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,49 @@ 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:
378
390
  estimated_p_yes = (
379
391
  answer.probability_for_market_outcome(direction)
380
392
  if not override_p_yes
381
393
  else override_p_yes
382
394
  )
383
395
 
384
- if not market.is_binary:
385
- # use Kelly simple, since Kelly full only supports 2 outcomes
386
-
396
+ if market.outcome_token_pool is None or self.force_simplified_calculation:
387
397
  kelly_bet = get_kelly_bet_simplified(
388
- max_bet=market.get_usd_in_token(max_bet_amount),
398
+ max_bet=market.get_usd_in_token(self.max_position_amount),
389
399
  market_p_yes=market.probability_for_market_outcome(direction),
390
400
  estimated_p_yes=estimated_p_yes,
391
401
  confidence=answer.confidence,
392
402
  )
393
403
  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
404
  direction_to_bet_pool_size = market.get_outcome_token_pool_by_outcome(
397
405
  direction
398
406
  )
@@ -403,7 +411,7 @@ class KellyBettingStrategy(BettingStrategy):
403
411
  yes_outcome_pool_size=direction_to_bet_pool_size,
404
412
  no_outcome_pool_size=other_direction_pool_size,
405
413
  estimated_p_yes=estimated_p_yes,
406
- max_bet=market.get_usd_in_token(max_bet_amount),
414
+ max_bet=market.get_usd_in_token(self.max_position_amount),
407
415
  confidence=answer.confidence,
408
416
  fees=market.fees,
409
417
  )
@@ -416,7 +424,7 @@ class KellyBettingStrategy(BettingStrategy):
416
424
  market: AgentMarket,
417
425
  ) -> list[Trade]:
418
426
  # We consider the p_yes as the direction with highest probability.
419
- direction = MultiCategoricalMaxAccuracyBettingStrategy.calculate_direction(
427
+ direction = CategoricalMaxAccuracyBettingStrategy.calculate_direction(
420
428
  market, answer
421
429
  )
422
430
  # We get the first direction which is != direction.
@@ -426,7 +434,6 @@ class KellyBettingStrategy(BettingStrategy):
426
434
 
427
435
  kelly_bet = self.get_kelly_bet(
428
436
  market=market,
429
- max_bet_amount=self.max_position_amount,
430
437
  direction=direction,
431
438
  other_direction=other_direction,
432
439
  answer=answer,
@@ -436,7 +443,10 @@ class KellyBettingStrategy(BettingStrategy):
436
443
  if self.max_price_impact:
437
444
  # Adjust amount
438
445
  max_price_impact_bet_amount = self.calculate_bet_amount_for_price_impact(
439
- market, kelly_bet, direction=direction
446
+ market,
447
+ kelly_bet.size,
448
+ direction=direction,
449
+ max_price_impact=self.max_price_impact,
440
450
  )
441
451
 
442
452
  # We just don't want Kelly size to extrapolate price_impact - hence we take the min.
@@ -455,8 +465,8 @@ class KellyBettingStrategy(BettingStrategy):
455
465
  )
456
466
  return trades
457
467
 
468
+ @staticmethod
458
469
  def calculate_price_impact_for_bet_amount(
459
- self,
460
470
  outcome_idx: int,
461
471
  bet_amount: CollateralToken,
462
472
  pool_balances: list[OutcomeWei],
@@ -474,29 +484,33 @@ class KellyBettingStrategy(BettingStrategy):
474
484
  price_impact = (actual_price - expected_price) / expected_price
475
485
  return price_impact
476
486
 
487
+ @staticmethod
477
488
  def calculate_bet_amount_for_price_impact(
478
- self, market: AgentMarket, kelly_bet: SimpleBet, direction: OutcomeStr
489
+ market: AgentMarket,
490
+ kelly_bet_size: CollateralToken,
491
+ direction: OutcomeStr,
492
+ max_price_impact: float,
479
493
  ) -> CollateralToken:
480
494
  def calculate_price_impact_deviation_from_target_price_impact(
481
495
  bet_amount_collateral: float, # Needs to be float because it's used in minimize_scalar internally.
482
496
  ) -> float:
483
497
  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,
498
+ price_impact = (
499
+ BinaryKellyBettingStrategy.calculate_price_impact_for_bet_amount(
500
+ outcome_idx=outcome_idx,
501
+ bet_amount=CollateralToken(bet_amount_collateral),
502
+ pool_balances=pool_balances,
503
+ fees=market.fees,
504
+ )
489
505
  )
490
506
  # 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
507
  return abs(price_impact - max_price_impact)
494
508
 
495
509
  if not market.outcome_token_pool:
496
510
  logger.warning(
497
511
  "Market outcome_token_pool is None, cannot calculate bet amount"
498
512
  )
499
- return kelly_bet.size
513
+ return kelly_bet_size
500
514
 
501
515
  pool_balances = [i.as_outcome_wei for i in market.outcome_token_pool.values()]
502
516
  # stay float for compatibility with `minimize_scalar`
@@ -513,7 +527,7 @@ class KellyBettingStrategy(BettingStrategy):
513
527
  return CollateralToken(optimized_bet_amount.x)
514
528
 
515
529
  def __repr__(self) -> str:
516
- return f"{self.__class__.__name__}(max_bet_amount={self.max_position_amount}, max_price_impact={self.max_price_impact})"
530
+ 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
531
 
518
532
 
519
533
  class MaxAccuracyWithKellyScaledBetsStrategy(BettingStrategy):
@@ -529,40 +543,29 @@ class MaxAccuracyWithKellyScaledBetsStrategy(BettingStrategy):
529
543
  def maximum_possible_bet_amount(self) -> USD:
530
544
  return self.max_position_amount
531
545
 
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
546
  def calculate_trades(
541
547
  self,
542
548
  existing_position: ExistingPosition | None,
543
549
  answer: CategoricalProbabilisticAnswer,
544
550
  market: AgentMarket,
545
551
  ) -> list[Trade]:
546
- adjusted_bet_amount_usd = self.adjust_bet_amount(existing_position, market)
547
-
548
552
  outcome = get_most_probable_outcome(answer.probabilities)
549
553
 
550
- direction = MultiCategoricalMaxAccuracyBettingStrategy.calculate_direction(
554
+ direction = CategoricalMaxAccuracyBettingStrategy.calculate_direction(
551
555
  market, answer
552
556
  )
553
557
  # We get the first direction which is != direction.
554
- other_direction = (
555
- MultiCategoricalMaxAccuracyBettingStrategy.get_other_direction(
556
- outcomes=market.outcomes, direction=direction
557
- )
558
+ other_direction = CategoricalMaxAccuracyBettingStrategy.get_other_direction(
559
+ outcomes=market.outcomes, direction=direction
558
560
  )
559
561
 
560
562
  # We ignore the direction nudge given by Kelly, hence we assume we have a perfect prediction.
561
563
  estimated_p_yes = 1.0
562
564
 
563
- kelly_bet = KellyBettingStrategy.get_kelly_bet(
565
+ kelly_bet = BinaryKellyBettingStrategy(
566
+ max_position_amount=self.max_position_amount
567
+ ).get_kelly_bet(
564
568
  market=market,
565
- max_bet_amount=adjusted_bet_amount_usd,
566
569
  direction=direction,
567
570
  other_direction=other_direction,
568
571
  answer=answer,
@@ -584,4 +587,118 @@ class MaxAccuracyWithKellyScaledBetsStrategy(BettingStrategy):
584
587
  return trades
585
588
 
586
589
  def __repr__(self) -> str:
587
- return f"{self.__class__.__name__}(max_bet_amount={self.max_position_amount})"
590
+ return f"{self.__class__.__name__}(max_position_amount={self.max_position_amount}, take_profit={self.take_profit})"
591
+
592
+
593
+ class CategoricalKellyBettingStrategy(BettingStrategy):
594
+ def __init__(
595
+ self,
596
+ max_position_amount: USD,
597
+ max_price_impact: float | None,
598
+ allow_multiple_bets: bool,
599
+ allow_shorting: bool,
600
+ multicategorical: bool,
601
+ take_profit: bool = True,
602
+ force_simplified_calculation: bool = False,
603
+ ):
604
+ super().__init__(take_profit=take_profit)
605
+ self.max_position_amount = max_position_amount
606
+ self.max_price_impact = max_price_impact
607
+ self.allow_multiple_bets = allow_multiple_bets
608
+ self.allow_shorting = allow_shorting
609
+ self.multicategorical = multicategorical
610
+ self.force_simplified_calculation = force_simplified_calculation
611
+
612
+ @property
613
+ def maximum_possible_bet_amount(self) -> USD:
614
+ return self.max_position_amount
615
+
616
+ def get_kelly_bets(
617
+ self,
618
+ market: AgentMarket,
619
+ max_bet_amount: USD,
620
+ answer: CategoricalProbabilisticAnswer,
621
+ ) -> list[CategoricalKellyBet]:
622
+ max_bet = market.get_usd_in_token(max_bet_amount)
623
+
624
+ if market.outcome_token_pool is None or self.force_simplified_calculation:
625
+ kelly_bets = get_kelly_bets_categorical_simplified(
626
+ market_probabilities=[market.probabilities[o] for o in market.outcomes],
627
+ estimated_probabilities=[
628
+ answer.probability_for_market_outcome(o) for o in market.outcomes
629
+ ],
630
+ confidence=answer.confidence,
631
+ max_bet=max_bet,
632
+ fees=market.fees,
633
+ allow_multiple_bets=self.allow_multiple_bets,
634
+ allow_shorting=self.allow_shorting,
635
+ )
636
+
637
+ else:
638
+ kelly_bets = get_kelly_bets_categorical_full(
639
+ outcome_pool_sizes=[
640
+ market.outcome_token_pool[o] for o in market.outcomes
641
+ ],
642
+ estimated_probabilities=[
643
+ answer.probability_for_market_outcome(o) for o in market.outcomes
644
+ ],
645
+ confidence=answer.confidence,
646
+ max_bet=max_bet,
647
+ fees=market.fees,
648
+ allow_multiple_bets=self.allow_multiple_bets,
649
+ allow_shorting=self.allow_shorting,
650
+ multicategorical=self.multicategorical,
651
+ )
652
+
653
+ return kelly_bets
654
+
655
+ def calculate_trades(
656
+ self,
657
+ existing_position: ExistingPosition | None,
658
+ answer: CategoricalProbabilisticAnswer,
659
+ market: AgentMarket,
660
+ ) -> list[Trade]:
661
+ kelly_bets = self.get_kelly_bets(
662
+ market=market,
663
+ max_bet_amount=self.max_position_amount,
664
+ answer=answer,
665
+ )
666
+
667
+ # TODO: Allow shorting in BettingStrategy._build_rebalance_trades_from_positions.
668
+ # In binary implementation, we simply flip the direction in case of negative bet, for categorical outcome, we need to implement shorting.
669
+ kelly_bets = [bet for bet in kelly_bets if bet.size > 0]
670
+ if not kelly_bets:
671
+ return []
672
+
673
+ # TODO: Allow betting on multiple outcomes.
674
+ # Categorical kelly could suggest to bet on multiple outcomes, but we only consider the first one for now (limitation of BettingStrategy `trades` creation).
675
+ # Also, this could maybe work for multi-categorical markets as well, but it wasn't benchmarked for it.
676
+ best_kelly_bet = max(kelly_bets, key=lambda x: abs(x.size))
677
+
678
+ if self.max_price_impact:
679
+ # Adjust amount
680
+ max_price_impact_bet_amount = (
681
+ BinaryKellyBettingStrategy.calculate_bet_amount_for_price_impact(
682
+ market,
683
+ best_kelly_bet.size,
684
+ direction=market.get_outcome_str(best_kelly_bet.index),
685
+ max_price_impact=self.max_price_impact,
686
+ )
687
+ )
688
+ # We just don't want Kelly size to extrapolate price_impact - hence we take the min.
689
+ best_kelly_bet.size = min(best_kelly_bet.size, max_price_impact_bet_amount)
690
+
691
+ amounts = {
692
+ market.outcomes[best_kelly_bet.index]: market.get_token_in_usd(
693
+ best_kelly_bet.size
694
+ ),
695
+ }
696
+ target_position = Position(market_id=market.id, amounts_current=amounts)
697
+ trades = self._build_rebalance_trades_from_positions(
698
+ existing_position, target_position, market=market
699
+ )
700
+
701
+ return trades
702
+
703
+ def __repr__(self) -> str:
704
+ 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
@@ -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 (
@@ -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.67.4.dev992
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=OfRat95Z1BwKFKVJ5lyToUi1Mheh14bs_bYxDw5vS9s,26217
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
@@ -81,9 +81,9 @@ prediction_market_agent_tooling/markets/seer/swap_pool_handler.py,sha256=k_sCEJZ
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
@@ -129,8 +129,8 @@ prediction_market_agent_tooling/tools/tokens/usd.py,sha256=yuW8iPPtcpP4eLH2nORMD
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
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,,
132
+ prediction_market_agent_tooling-0.67.4.dev992.dist-info/LICENSE,sha256=6or154nLLU6bELzjh0mCreFjt0m2v72zLi3yHE0QbeE,7650
133
+ prediction_market_agent_tooling-0.67.4.dev992.dist-info/METADATA,sha256=Aet2qwsvChkVHwUFwQrc9EswNQ7Ba4Ko6h3Jp6t-DUg,8777
134
+ prediction_market_agent_tooling-0.67.4.dev992.dist-info/WHEEL,sha256=b4K_helf-jlQoXBBETfwnf4B04YC67LOev0jo4fX5m8,88
135
+ prediction_market_agent_tooling-0.67.4.dev992.dist-info/entry_points.txt,sha256=m8PukHbeH5g0IAAmOf_1Ahm-sGAMdhSSRQmwtpmi2s8,81
136
+ prediction_market_agent_tooling-0.67.4.dev992.dist-info/RECORD,,