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