prediction-market-agent-tooling 0.53.0__py3-none-any.whl → 0.55.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.
@@ -8,10 +8,8 @@ from datetime import timedelta
8
8
  from enum import Enum
9
9
  from functools import cached_property
10
10
 
11
- from pydantic import BaseModel, BeforeValidator, computed_field
11
+ from pydantic import BeforeValidator, computed_field
12
12
  from typing_extensions import Annotated
13
- from web3 import Web3
14
- from web3.constants import HASH_ZERO
15
13
 
16
14
  from prediction_market_agent_tooling.config import APIKeys
17
15
  from prediction_market_agent_tooling.deploy.betting_strategy import (
@@ -32,11 +30,13 @@ from prediction_market_agent_tooling.deploy.gcp.utils import (
32
30
  gcp_function_is_active,
33
31
  gcp_resolve_api_keys_secrets,
34
32
  )
35
- from prediction_market_agent_tooling.gtypes import HexStr, xDai, xdai_type
33
+ from prediction_market_agent_tooling.gtypes import xDai, xdai_type
36
34
  from prediction_market_agent_tooling.loggers import logger
37
35
  from prediction_market_agent_tooling.markets.agent_market import (
38
36
  AgentMarket,
39
37
  FilterBy,
38
+ ProcessedMarket,
39
+ ProcessedTradedMarket,
40
40
  SortBy,
41
41
  )
42
42
  from prediction_market_agent_tooling.markets.data_models import (
@@ -49,28 +49,16 @@ from prediction_market_agent_tooling.markets.markets import (
49
49
  MarketType,
50
50
  have_bet_on_market_since,
51
51
  )
52
- from prediction_market_agent_tooling.markets.omen.data_models import (
53
- ContractPrediction,
54
- IPFSAgentResult,
55
- )
56
52
  from prediction_market_agent_tooling.markets.omen.omen import (
57
- is_minimum_required_balance,
58
- redeem_from_all_user_positions,
59
53
  withdraw_wxdai_to_xdai_to_keep_balance,
60
54
  )
61
- from prediction_market_agent_tooling.markets.omen.omen_contracts import (
62
- OmenAgentResultMappingContract,
63
- )
64
55
  from prediction_market_agent_tooling.monitor.monitor_app import (
65
56
  MARKET_TYPE_TO_DEPLOYED_AGENT,
66
57
  )
67
- from prediction_market_agent_tooling.tools.hexbytes_custom import HexBytes
68
- from prediction_market_agent_tooling.tools.ipfs.ipfs_handler import IPFSHandler
69
58
  from prediction_market_agent_tooling.tools.is_invalid import is_invalid
70
59
  from prediction_market_agent_tooling.tools.is_predictable import is_predictable_binary
71
60
  from prediction_market_agent_tooling.tools.langfuse_ import langfuse_context, observe
72
61
  from prediction_market_agent_tooling.tools.utils import DatetimeUTC, utcnow
73
- from prediction_market_agent_tooling.tools.web3_utils import ipfscidv0_to_byte32
74
62
 
75
63
  MAX_AVAILABLE_MARKETS = 20
76
64
  TRADER_TAG = "trader"
@@ -122,11 +110,6 @@ class OutOfFundsError(ValueError):
122
110
  pass
123
111
 
124
112
 
125
- class ProcessedMarket(BaseModel):
126
- answer: ProbabilisticAnswer
127
- trades: list[PlacedTrade]
128
-
129
-
130
113
  class AnsweredEnum(str, Enum):
131
114
  ANSWERED = "answered"
132
115
  NOT_ANSWERED = "not_answered"
@@ -183,9 +166,11 @@ class DeployableAgent:
183
166
  return f"{self.__class__.__name__} - {self.start_time.strftime('%Y-%m-%d %H:%M:%S')}"
184
167
 
185
168
  def __init_subclass__(cls, **kwargs: t.Any) -> None:
186
- if "DeployableAgent" not in str(
187
- cls.__init__
188
- ) and "DeployableTraderAgent" not in str(cls.__init__):
169
+ if (
170
+ "DeployableAgent" not in str(cls.__init__)
171
+ and "DeployableTraderAgent" not in str(cls.__init__)
172
+ and "DeployablePredictionAgent" not in str(cls.__init__)
173
+ ):
189
174
  raise TypeError(
190
175
  "Cannot override __init__ method of deployable agent class, please override the `load` method to set up the agent."
191
176
  )
@@ -292,29 +277,21 @@ def {entrypoint_function_name}(request) -> str:
292
277
  return f"{self.__class__.__name__.lower()}-{market_type}-{utcnow().strftime('%Y-%m-%d--%H-%M-%S')}"
293
278
 
294
279
 
295
- class DeployableTraderAgent(DeployableAgent):
280
+ class DeployablePredictionAgent(DeployableAgent):
296
281
  bet_on_n_markets_per_run: int = 1
297
- min_required_balance_to_operate: xDai | None = xdai_type(1)
298
282
  min_balance_to_keep_in_native_currency: xDai | None = xdai_type(0.1)
299
283
  allow_invalid_questions: bool = False
300
284
  same_market_bet_interval: timedelta = timedelta(hours=24)
285
+ # Only Metaculus allows to post predictions without trading (buying/selling of outcome tokens).
286
+ supported_markets: t.Sequence[MarketType] = [MarketType.METACULUS]
301
287
 
302
288
  def __init__(
303
289
  self,
304
290
  enable_langfuse: bool = APIKeys().default_enable_langfuse,
305
- place_bet: bool = True,
291
+ store_prediction: bool = True,
306
292
  ) -> None:
307
293
  super().__init__(enable_langfuse=enable_langfuse)
308
- self.place_bet = place_bet
309
-
310
- def get_betting_strategy(self, market: AgentMarket) -> BettingStrategy:
311
- user_id = market.get_user_id(api_keys=APIKeys())
312
-
313
- total_amount = market.get_tiny_bet_amount().amount
314
- if existing_position := market.get_position(user_id=user_id):
315
- total_amount += existing_position.total_amount.amount
316
-
317
- return MaxAccuracyBettingStrategy(bet_amount=total_amount)
294
+ self.store_prediction = store_prediction
318
295
 
319
296
  def initialize_langfuse(self) -> None:
320
297
  super().initialize_langfuse()
@@ -323,7 +300,6 @@ class DeployableTraderAgent(DeployableAgent):
323
300
  self.verify_market = observe()(self.verify_market) # type: ignore[method-assign]
324
301
  self.answer_binary_market = observe()(self.answer_binary_market) # type: ignore[method-assign]
325
302
  self.process_market = observe()(self.process_market) # type: ignore[method-assign]
326
- self.build_trades = observe()(self.build_trades) # type: ignore[method-assign]
327
303
 
328
304
  def update_langfuse_trace_by_market(
329
305
  self, market_type: MarketType, market: AgentMarket
@@ -353,38 +329,12 @@ class DeployableTraderAgent(DeployableAgent):
353
329
  ]
354
330
  )
355
331
 
356
- def check_min_required_balance_to_operate(
357
- self,
358
- market_type: MarketType,
359
- check_for_gas: bool = True,
360
- check_for_trades: bool = True,
361
- ) -> None:
332
+ def check_min_required_balance_to_operate(self, market_type: MarketType) -> None:
362
333
  api_keys = APIKeys()
363
- if (
364
- market_type == MarketType.OMEN
365
- and check_for_gas
366
- and not is_minimum_required_balance(
367
- api_keys.public_key,
368
- min_required_balance=xdai_type(0.001),
369
- sum_wxdai=False,
370
- )
371
- ):
334
+
335
+ if not market_type.market_class.verify_operational_balance(api_keys):
372
336
  raise CantPayForGasError(
373
- f"{api_keys.public_key=} doesn't have enough xDai to pay for gas."
374
- )
375
- if self.min_required_balance_to_operate is None:
376
- return
377
- if (
378
- market_type == MarketType.OMEN
379
- and check_for_trades
380
- and not is_minimum_required_balance(
381
- api_keys.bet_from_address,
382
- min_required_balance=self.min_required_balance_to_operate,
383
- )
384
- ):
385
- raise OutOfFundsError(
386
- f"Minimum required balance {self.min_required_balance_to_operate} "
387
- f"for agent with address {api_keys.bet_from_address=} is not met."
337
+ f"{api_keys=} doesn't have enough operational balance."
388
338
  )
389
339
 
390
340
  def have_bet_on_market_since(self, market: AgentMarket, since: timedelta) -> bool:
@@ -431,21 +381,19 @@ class DeployableTraderAgent(DeployableAgent):
431
381
  )
432
382
  return available_markets
433
383
 
434
- def build_trades(
435
- self,
436
- market: AgentMarket,
437
- answer: ProbabilisticAnswer,
438
- existing_position: Position | None,
439
- ) -> list[Trade]:
440
- strategy = self.get_betting_strategy(market=market)
441
- trades = strategy.calculate_trades(existing_position, answer, market)
442
- BettingStrategy.assert_trades_currency_match_markets(market, trades)
443
- return trades
444
-
445
384
  def before_process_market(
446
385
  self, market_type: MarketType, market: AgentMarket
447
386
  ) -> None:
448
- self.update_langfuse_trace_by_market(market_type, market)
387
+ api_keys = APIKeys()
388
+
389
+ if market_type.is_blockchain_market:
390
+ # Exchange wxdai back to xdai if the balance is getting low, so we can keep paying for fees.
391
+ if self.min_balance_to_keep_in_native_currency is not None:
392
+ withdraw_wxdai_to_xdai_to_keep_balance(
393
+ api_keys,
394
+ min_required_balance=self.min_balance_to_keep_in_native_currency,
395
+ withdraw_multiplier=2,
396
+ )
449
397
 
450
398
  def process_market(
451
399
  self,
@@ -453,129 +401,47 @@ class DeployableTraderAgent(DeployableAgent):
453
401
  market: AgentMarket,
454
402
  verify_market: bool = True,
455
403
  ) -> ProcessedMarket | None:
404
+ self.update_langfuse_trace_by_market(market_type, market)
456
405
  logger.info(f"Processing market {market.question=} from {market.url=}.")
457
406
 
458
- self.before_process_market(market_type, market)
459
-
407
+ answer: ProbabilisticAnswer | None
460
408
  if verify_market and not self.verify_market(market_type, market):
461
409
  logger.info(f"Market '{market.question}' doesn't meet the criteria.")
462
- self.update_langfuse_trace_by_processed_market(market_type, None)
463
- return None
464
-
465
- answer = self.answer_binary_market(market)
466
-
467
- if answer is None:
468
- logger.info(f"No answer for market '{market.question}'.")
469
- self.update_langfuse_trace_by_processed_market(market_type, None)
470
- return None
410
+ answer = None
411
+ else:
412
+ answer = self.answer_binary_market(market)
471
413
 
472
- existing_position = market.get_position(user_id=APIKeys().bet_from_address)
473
- trades = self.build_trades(
474
- market=market,
475
- answer=answer,
476
- existing_position=existing_position,
414
+ processed_market = (
415
+ ProcessedMarket(answer=answer) if answer is not None else None
477
416
  )
478
417
 
479
- placed_trades = []
480
- if self.place_bet:
481
- for trade in trades:
482
- logger.info(f"Executing trade {trade} on market {market.id}")
483
-
484
- match trade.trade_type:
485
- case TradeType.BUY:
486
- id = market.buy_tokens(
487
- outcome=trade.outcome, amount=trade.amount
488
- )
489
- case TradeType.SELL:
490
- id = market.sell_tokens(
491
- outcome=trade.outcome, amount=trade.amount
492
- )
493
- case _:
494
- raise ValueError(f"Unexpected trade type {trade.trade_type}.")
495
- placed_trades.append(PlacedTrade.from_trade(trade, id))
496
-
497
- processed_market = ProcessedMarket(answer=answer, trades=placed_trades)
498
418
  self.update_langfuse_trace_by_processed_market(market_type, processed_market)
499
-
500
- self.after_process_market(
501
- market_type, market, processed_market=processed_market
419
+ logger.info(
420
+ f"Processed market {market.question=} from {market.url=} with {answer=}."
502
421
  )
503
-
504
- logger.info(f"Processed market {market.question=} from {market.url=}.")
505
422
  return processed_market
506
423
 
507
424
  def after_process_market(
508
425
  self,
509
426
  market_type: MarketType,
510
427
  market: AgentMarket,
511
- processed_market: ProcessedMarket,
428
+ processed_market: ProcessedMarket | None,
512
429
  ) -> None:
513
- if market_type != MarketType.OMEN:
514
- logger.info(
515
- f"Skipping after_process_market since market_type {market_type} != OMEN"
516
- )
517
- return
518
430
  keys = APIKeys()
519
- self.store_prediction(
520
- market_id=market.id, processed_market=processed_market, keys=keys
521
- )
522
-
523
- def store_prediction(
524
- self, market_id: str, processed_market: ProcessedMarket, keys: APIKeys
525
- ) -> None:
526
- reasoning = (
527
- processed_market.answer.reasoning
528
- if processed_market.answer.reasoning
529
- else ""
530
- )
531
-
532
- ipfs_hash_decoded = HexBytes(HASH_ZERO)
533
- if keys.enable_ipfs_upload:
534
- logger.info("Storing prediction on IPFS.")
535
- ipfs_hash = IPFSHandler(keys).store_agent_result(
536
- IPFSAgentResult(reasoning=reasoning)
431
+ if self.store_prediction:
432
+ market.store_prediction(processed_market=processed_market, keys=keys)
433
+ else:
434
+ logger.info(
435
+ f"Prediction {processed_market} not stored because {self.store_prediction=}."
537
436
  )
538
- ipfs_hash_decoded = ipfscidv0_to_byte32(ipfs_hash)
539
-
540
- tx_hashes = [
541
- HexBytes(HexStr(i.id)) for i in processed_market.trades if i.id is not None
542
- ]
543
- prediction = ContractPrediction(
544
- publisher=keys.public_key,
545
- ipfs_hash=ipfs_hash_decoded,
546
- tx_hashes=tx_hashes,
547
- estimated_probability_bps=int(processed_market.answer.p_yes * 10000),
548
- )
549
- tx_receipt = OmenAgentResultMappingContract().add_prediction(
550
- api_keys=keys,
551
- market_address=Web3.to_checksum_address(market_id),
552
- prediction=prediction,
553
- )
554
- logger.info(
555
- f"Added prediction to market {market_id}. - receipt {tx_receipt['transactionHash'].hex()}."
556
- )
557
437
 
558
438
  def before_process_markets(self, market_type: MarketType) -> None:
559
439
  """
560
440
  Executes actions that occur before bets are placed.
561
441
  """
562
442
  api_keys = APIKeys()
563
- if market_type == MarketType.OMEN:
564
- # First, check if we have enough xDai to pay for gas, there is no way of doing anything without it.
565
- self.check_min_required_balance_to_operate(
566
- market_type, check_for_trades=False
567
- )
568
- # Omen is specific, because the user (agent) needs to manually withdraw winnings from the market.
569
- redeem_from_all_user_positions(api_keys)
570
- # After redeeming, check if we have enough xDai to pay for gas and place bets.
571
- self.check_min_required_balance_to_operate(market_type)
572
- # Exchange wxdai back to xdai if the balance is getting low, so we can keep paying for fees.
573
- if self.min_balance_to_keep_in_native_currency is not None:
574
- withdraw_wxdai_to_xdai_to_keep_balance(
575
- api_keys,
576
- min_required_balance=self.min_balance_to_keep_in_native_currency,
577
- withdraw_multiplier=2,
578
- )
443
+ self.check_min_required_balance_to_operate(market_type)
444
+ market_type.market_class.redeem_winnings(api_keys)
579
445
 
580
446
  def process_markets(self, market_type: MarketType) -> None:
581
447
  """
@@ -589,10 +455,9 @@ class DeployableTraderAgent(DeployableAgent):
589
455
  processed = 0
590
456
 
591
457
  for market in available_markets:
592
- # We need to check it again before each market bet, as the balance might have changed.
593
- self.check_min_required_balance_to_operate(market_type)
594
-
458
+ self.before_process_market(market_type, market)
595
459
  processed_market = self.process_market(market_type, market)
460
+ self.after_process_market(market_type, market, processed_market)
596
461
 
597
462
  if processed_market is not None:
598
463
  processed += 1
@@ -603,9 +468,142 @@ class DeployableTraderAgent(DeployableAgent):
603
468
  logger.info("All markets processed.")
604
469
 
605
470
  def after_process_markets(self, market_type: MarketType) -> None:
606
- pass
471
+ "Executes actions that occur after bets are placed."
607
472
 
608
473
  def run(self, market_type: MarketType) -> None:
474
+ if market_type not in self.supported_markets:
475
+ raise ValueError(
476
+ f"Only {self.supported_markets} are supported by this agent."
477
+ )
609
478
  self.before_process_markets(market_type)
610
479
  self.process_markets(market_type)
611
480
  self.after_process_markets(market_type)
481
+
482
+
483
+ class DeployableTraderAgent(DeployablePredictionAgent):
484
+ # These markets require place of bet, not just predictions.
485
+ supported_markets: t.Sequence[MarketType] = [
486
+ MarketType.OMEN,
487
+ MarketType.MANIFOLD,
488
+ MarketType.POLYMARKET,
489
+ ]
490
+
491
+ def __init__(
492
+ self,
493
+ enable_langfuse: bool = APIKeys().default_enable_langfuse,
494
+ store_prediction: bool = True,
495
+ store_trades: bool = True,
496
+ place_trades: bool = True,
497
+ ) -> None:
498
+ super().__init__(
499
+ enable_langfuse=enable_langfuse, store_prediction=store_prediction
500
+ )
501
+ self.store_trades = store_trades
502
+ self.place_trades = place_trades
503
+
504
+ def initialize_langfuse(self) -> None:
505
+ super().initialize_langfuse()
506
+ # Auto-observe all the methods where it makes sense, so that subclassses don't need to do it manually.
507
+ self.get_betting_strategy = observe()(self.get_betting_strategy) # type: ignore[method-assign]
508
+ self.build_trades = observe()(self.build_trades) # type: ignore[method-assign]
509
+
510
+ def check_min_required_balance_to_trade(self, market: AgentMarket) -> None:
511
+ api_keys = APIKeys()
512
+
513
+ # Get the strategy to know how much it will bet.
514
+ strategy = self.get_betting_strategy(market)
515
+ # Have a little bandwidth after the bet.
516
+ min_required_balance_to_trade = strategy.maximum_possible_bet_amount * 1.01
517
+
518
+ if market.get_trade_balance(api_keys) < min_required_balance_to_trade:
519
+ raise OutOfFundsError(
520
+ f"Minimum required balance {min_required_balance_to_trade} for agent is not met."
521
+ )
522
+
523
+ def get_betting_strategy(self, market: AgentMarket) -> BettingStrategy:
524
+ user_id = market.get_user_id(api_keys=APIKeys())
525
+
526
+ total_amount = market.get_tiny_bet_amount().amount
527
+ if existing_position := market.get_position(user_id=user_id):
528
+ total_amount += existing_position.total_amount.amount
529
+
530
+ return MaxAccuracyBettingStrategy(bet_amount=total_amount)
531
+
532
+ def build_trades(
533
+ self,
534
+ market: AgentMarket,
535
+ answer: ProbabilisticAnswer,
536
+ existing_position: Position | None,
537
+ ) -> list[Trade]:
538
+ strategy = self.get_betting_strategy(market=market)
539
+ trades = strategy.calculate_trades(existing_position, answer, market)
540
+ BettingStrategy.assert_trades_currency_match_markets(market, trades)
541
+ return trades
542
+
543
+ def before_process_market(
544
+ self, market_type: MarketType, market: AgentMarket
545
+ ) -> None:
546
+ super().before_process_market(market_type, market)
547
+ self.check_min_required_balance_to_trade(market)
548
+
549
+ def process_market(
550
+ self,
551
+ market_type: MarketType,
552
+ market: AgentMarket,
553
+ verify_market: bool = True,
554
+ ) -> ProcessedTradedMarket | None:
555
+ processed_market = super().process_market(market_type, market, verify_market)
556
+ if processed_market is None:
557
+ return None
558
+
559
+ api_keys = APIKeys()
560
+ existing_position = market.get_position(
561
+ user_id=market.get_user_id(api_keys=api_keys)
562
+ )
563
+ trades = self.build_trades(
564
+ market=market,
565
+ answer=processed_market.answer,
566
+ existing_position=existing_position,
567
+ )
568
+
569
+ placed_trades = []
570
+ for trade in trades:
571
+ logger.info(f"Executing trade {trade} on market {market.id} ({market.url})")
572
+
573
+ if self.place_trades:
574
+ match trade.trade_type:
575
+ case TradeType.BUY:
576
+ id = market.buy_tokens(
577
+ outcome=trade.outcome, amount=trade.amount
578
+ )
579
+ case TradeType.SELL:
580
+ id = market.sell_tokens(
581
+ outcome=trade.outcome, amount=trade.amount
582
+ )
583
+ case _:
584
+ raise ValueError(f"Unexpected trade type {trade.trade_type}.")
585
+ placed_trades.append(PlacedTrade.from_trade(trade, id))
586
+ else:
587
+ logger.info(f"Trade execution skipped because {self.place_trades=}.")
588
+
589
+ traded_market = ProcessedTradedMarket(
590
+ answer=processed_market.answer, trades=placed_trades
591
+ )
592
+ logger.info(f"Traded market {market.question=} from {market.url=}.")
593
+ return traded_market
594
+
595
+ def after_process_market(
596
+ self,
597
+ market_type: MarketType,
598
+ market: AgentMarket,
599
+ processed_market: ProcessedMarket | None,
600
+ ) -> None:
601
+ api_keys = APIKeys()
602
+ super().after_process_market(market_type, market, processed_market)
603
+ if isinstance(processed_market, ProcessedTradedMarket):
604
+ if self.store_trades:
605
+ market.store_trades(processed_market, api_keys)
606
+ else:
607
+ logger.info(
608
+ f"Trades {processed_market.trades} not stored because {self.store_trades=}."
609
+ )
@@ -24,6 +24,10 @@ from prediction_market_agent_tooling.tools.betting_strategies.utils import Simpl
24
24
  from prediction_market_agent_tooling.tools.utils import check_not_none
25
25
 
26
26
 
27
+ class GuaranteedLossError(RuntimeError):
28
+ pass
29
+
30
+
27
31
  class BettingStrategy(ABC):
28
32
  @abstractmethod
29
33
  def calculate_trades(
@@ -32,7 +36,12 @@ class BettingStrategy(ABC):
32
36
  answer: ProbabilisticAnswer,
33
37
  market: AgentMarket,
34
38
  ) -> list[Trade]:
35
- pass
39
+ raise NotImplementedError("Subclass should implement this.")
40
+
41
+ @property
42
+ @abstractmethod
43
+ def maximum_possible_bet_amount(self) -> float:
44
+ raise NotImplementedError("Subclass should implement this.")
36
45
 
37
46
  def build_zero_token_amount(self, currency: Currency) -> TokenAmount:
38
47
  return TokenAmount(amount=0, currency=currency)
@@ -58,7 +67,7 @@ class BettingStrategy(ABC):
58
67
  )
59
68
 
60
69
  if outcome_tokens_to_get.amount < trade.amount.amount:
61
- raise RuntimeError(
70
+ raise GuaranteedLossError(
62
71
  f"Trade {trade=} would result in guaranteed loss by getting only {outcome_tokens_to_get=}."
63
72
  )
64
73
 
@@ -126,6 +135,10 @@ class MaxAccuracyBettingStrategy(BettingStrategy):
126
135
  def __init__(self, bet_amount: float):
127
136
  self.bet_amount = bet_amount
128
137
 
138
+ @property
139
+ def maximum_possible_bet_amount(self) -> float:
140
+ return self.bet_amount
141
+
129
142
  def calculate_trades(
130
143
  self,
131
144
  existing_position: Position | None,
@@ -168,6 +181,10 @@ class KellyBettingStrategy(BettingStrategy):
168
181
  self.max_bet_amount = max_bet_amount
169
182
  self.max_price_impact = max_price_impact
170
183
 
184
+ @property
185
+ def maximum_possible_bet_amount(self) -> float:
186
+ return self.max_bet_amount
187
+
171
188
  def calculate_trades(
172
189
  self,
173
190
  existing_position: Position | None,
@@ -282,6 +299,10 @@ class MaxAccuracyWithKellyScaledBetsStrategy(BettingStrategy):
282
299
  def __init__(self, max_bet_amount: float = 10):
283
300
  self.max_bet_amount = max_bet_amount
284
301
 
302
+ @property
303
+ def maximum_possible_bet_amount(self) -> float:
304
+ return self.max_bet_amount
305
+
285
306
  def adjust_bet_amount(
286
307
  self, existing_position: Position | None, market: AgentMarket
287
308
  ) -> float:
@@ -11,7 +11,9 @@ from prediction_market_agent_tooling.markets.data_models import (
11
11
  Bet,
12
12
  BetAmount,
13
13
  Currency,
14
+ PlacedTrade,
14
15
  Position,
16
+ ProbabilisticAnswer,
15
17
  Resolution,
16
18
  ResolvedBet,
17
19
  TokenAmount,
@@ -25,6 +27,14 @@ from prediction_market_agent_tooling.tools.utils import (
25
27
  )
26
28
 
27
29
 
30
+ class ProcessedMarket(BaseModel):
31
+ answer: ProbabilisticAnswer
32
+
33
+
34
+ class ProcessedTradedMarket(ProcessedMarket):
35
+ trades: list[PlacedTrade]
36
+
37
+
28
38
  class SortBy(str, Enum):
29
39
  CLOSING_SOONEST = "closing-soonest"
30
40
  NEWEST = "newest"
@@ -198,6 +208,44 @@ class AgentMarket(BaseModel):
198
208
  def get_binary_market(id: str) -> "AgentMarket":
199
209
  raise NotImplementedError("Subclasses must implement this method")
200
210
 
211
+ @staticmethod
212
+ def redeem_winnings(api_keys: APIKeys) -> None:
213
+ """
214
+ On some markets (like Omen), it's needed to manually claim the winner bets. If it's not needed, just implement with `pass`.
215
+ """
216
+ raise NotImplementedError("Subclasses must implement this method")
217
+
218
+ @staticmethod
219
+ def get_trade_balance(api_keys: APIKeys) -> float:
220
+ """
221
+ Return balance that can be used to trade on the given market.
222
+ """
223
+ raise NotImplementedError("Subclasses must implement this method")
224
+
225
+ @staticmethod
226
+ def verify_operational_balance(api_keys: APIKeys) -> bool:
227
+ """
228
+ Return `True` if the user has enough of operational balance. If not needed, just return `True`.
229
+ For example: Omen needs at least some xDai in the wallet to execute transactions.
230
+ """
231
+ raise NotImplementedError("Subclasses must implement this method")
232
+
233
+ def store_prediction(
234
+ self, processed_market: ProcessedMarket | None, keys: APIKeys
235
+ ) -> None:
236
+ """
237
+ If market allows to upload predictions somewhere, implement it in this method.
238
+ """
239
+ raise NotImplementedError("Subclasses must implement this method")
240
+
241
+ def store_trades(
242
+ self, traded_market: ProcessedTradedMarket | None, keys: APIKeys
243
+ ) -> None:
244
+ """
245
+ If market allows to upload trades somewhere, implement it in this method.
246
+ """
247
+ raise NotImplementedError("Subclasses must implement this method")
248
+
201
249
  @staticmethod
202
250
  def get_bets_made_since(
203
251
  better_address: ChecksumAddress, start_time: DatetimeUTC
@@ -125,6 +125,11 @@ class ManifoldAgentMarket(AgentMarket):
125
125
  )
126
126
  ]
127
127
 
128
+ @staticmethod
129
+ def redeem_winnings(api_keys: APIKeys) -> None:
130
+ # It's done automatically on Manifold.
131
+ pass
132
+
128
133
  @classmethod
129
134
  def get_user_url(cls, keys: APIKeys) -> str:
130
135
  return get_authenticated_user(keys.manifold_api_key.get_secret_value()).url
@@ -46,6 +46,10 @@ class MarketType(str, Enum):
46
46
  raise ValueError(f"Unknown market type: {self}")
47
47
  return MARKET_TYPE_TO_AGENT_MARKET[self]
48
48
 
49
+ @property
50
+ def is_blockchain_market(self) -> bool:
51
+ return self in [MarketType.OMEN, MarketType.POLYMARKET]
52
+
49
53
 
50
54
  MARKET_TYPE_TO_AGENT_MARKET: dict[MarketType, type[AgentMarket]] = {
51
55
  MarketType.MANIFOLD: ManifoldAgentMarket,
@@ -56,6 +56,9 @@ class Question(BaseModel):
56
56
  my_forecasts: MyAggregation
57
57
  type: QuestionType
58
58
  possibilities: dict[str, str] | None
59
+ description: str
60
+ fine_print: str
61
+ resolution_criteria: str
59
62
 
60
63
 
61
64
  class MetaculusQuestion(BaseModel):
@@ -64,7 +67,7 @@ class MetaculusQuestion(BaseModel):
64
67
  author_username: str
65
68
  title: str
66
69
  created_at: DatetimeUTC
67
- published_at: DatetimeUTC
70
+ published_at: DatetimeUTC | None
68
71
  scheduled_close_time: DatetimeUTC
69
72
  scheduled_resolve_time: DatetimeUTC
70
73
  user_permission: str
@@ -1,11 +1,11 @@
1
1
  import typing as t
2
2
 
3
3
  from prediction_market_agent_tooling.config import APIKeys
4
- from prediction_market_agent_tooling.gtypes import Probability
5
4
  from prediction_market_agent_tooling.markets.agent_market import (
6
5
  AgentMarket,
7
6
  FilterBy,
8
7
  MarketFees,
8
+ ProcessedMarket,
9
9
  SortBy,
10
10
  )
11
11
  from prediction_market_agent_tooling.markets.metaculus.api import (
@@ -17,7 +17,7 @@ from prediction_market_agent_tooling.markets.metaculus.api import (
17
17
  from prediction_market_agent_tooling.markets.metaculus.data_models import (
18
18
  MetaculusQuestion,
19
19
  )
20
- from prediction_market_agent_tooling.tools.utils import DatetimeUTC
20
+ from prediction_market_agent_tooling.tools.utils import DatetimeUTC, check_not_none
21
21
 
22
22
 
23
23
  class MetaculusAgentMarket(AgentMarket):
@@ -27,9 +27,9 @@ class MetaculusAgentMarket(AgentMarket):
27
27
 
28
28
  have_predicted: bool
29
29
  base_url: t.ClassVar[str] = METACULUS_API_BASE_URL
30
- description: str | None = (
31
- None # Metaculus markets don't have a description, so just default to None.
32
- )
30
+ description: str
31
+ fine_print: str
32
+ resolution_criteria: str
33
33
  fees: MarketFees = MarketFees.get_zero_fees() # No fees on Metaculus.
34
34
 
35
35
  @staticmethod
@@ -46,6 +46,9 @@ class MetaculusAgentMarket(AgentMarket):
46
46
  volume=None,
47
47
  have_predicted=model.question.my_forecasts.latest is not None,
48
48
  outcome_token_pool=None,
49
+ description=model.question.description,
50
+ fine_print=model.question.fine_print,
51
+ resolution_criteria=model.question.resolution_criteria,
49
52
  )
50
53
 
51
54
  @staticmethod
@@ -103,10 +106,29 @@ class MetaculusAgentMarket(AgentMarket):
103
106
  break
104
107
  return [MetaculusAgentMarket.from_data_model(q) for q in all_questions[:limit]]
105
108
 
106
- def submit_prediction(self, p_yes: Probability, reasoning: str) -> None:
107
- make_prediction(self.id, p_yes)
108
- post_question_comment(self.id, reasoning)
109
+ def store_prediction(
110
+ self, processed_market: ProcessedMarket | None, keys: APIKeys
111
+ ) -> None:
112
+ if processed_market is not None:
113
+ make_prediction(self.id, processed_market.answer.p_yes)
114
+ post_question_comment(
115
+ self.id,
116
+ check_not_none(
117
+ processed_market.answer.reasoning,
118
+ "Reasoning must be provided for Metaculus.",
119
+ ),
120
+ )
109
121
 
110
122
  @staticmethod
111
123
  def get_user_id(api_keys: APIKeys) -> str:
112
124
  return str(api_keys.metaculus_user_id)
125
+
126
+ @staticmethod
127
+ def verify_operational_balance(api_keys: APIKeys) -> bool:
128
+ # No operational balance for Metaculus.
129
+ return True
130
+
131
+ @staticmethod
132
+ def redeem_winnings(api_keys: APIKeys) -> None:
133
+ # Nothing to redeem on Metaculus.
134
+ pass
@@ -4,6 +4,7 @@ from datetime import timedelta
4
4
 
5
5
  import tenacity
6
6
  from web3 import Web3
7
+ from web3.constants import HASH_ZERO
7
8
 
8
9
  from prediction_market_agent_tooling.config import APIKeys
9
10
  from prediction_market_agent_tooling.gtypes import (
@@ -23,6 +24,8 @@ from prediction_market_agent_tooling.markets.agent_market import (
23
24
  AgentMarket,
24
25
  FilterBy,
25
26
  MarketFees,
27
+ ProcessedMarket,
28
+ ProcessedTradedMarket,
26
29
  SortBy,
27
30
  )
28
31
  from prediction_market_agent_tooling.markets.data_models import (
@@ -39,7 +42,9 @@ from prediction_market_agent_tooling.markets.omen.data_models import (
39
42
  PRESAGIO_BASE_URL,
40
43
  Condition,
41
44
  ConditionPreparationEvent,
45
+ ContractPrediction,
42
46
  CreatedMarket,
47
+ IPFSAgentResult,
43
48
  OmenBet,
44
49
  OmenMarket,
45
50
  OmenUserPosition,
@@ -50,6 +55,7 @@ from prediction_market_agent_tooling.markets.omen.omen_contracts import (
50
55
  OMEN_DEFAULT_MARKET_FEE_PERC,
51
56
  REALITY_DEFAULT_FINALIZATION_TIMEOUT,
52
57
  Arbitrator,
58
+ OmenAgentResultMappingContract,
53
59
  OmenConditionalTokenContract,
54
60
  OmenFixedProductMarketMakerContract,
55
61
  OmenFixedProductMarketMakerFactoryContract,
@@ -70,6 +76,7 @@ from prediction_market_agent_tooling.tools.contract import (
70
76
  to_gnosis_chain_contract,
71
77
  )
72
78
  from prediction_market_agent_tooling.tools.hexbytes_custom import HexBytes
79
+ from prediction_market_agent_tooling.tools.ipfs.ipfs_handler import IPFSHandler
73
80
  from prediction_market_agent_tooling.tools.utils import (
74
81
  DatetimeUTC,
75
82
  calculate_sell_amount_in_collateral,
@@ -78,6 +85,7 @@ from prediction_market_agent_tooling.tools.utils import (
78
85
  from prediction_market_agent_tooling.tools.web3_utils import (
79
86
  add_fraction,
80
87
  get_receipt_block_timestamp,
88
+ ipfscidv0_to_byte32,
81
89
  remove_fraction,
82
90
  wei_to_xdai,
83
91
  xdai_to_wei,
@@ -392,6 +400,65 @@ class OmenAgentMarket(AgentMarket):
392
400
  )
393
401
  )
394
402
 
403
+ @staticmethod
404
+ def redeem_winnings(api_keys: APIKeys) -> None:
405
+ redeem_from_all_user_positions(api_keys)
406
+
407
+ @staticmethod
408
+ def get_trade_balance(api_keys: APIKeys, web3: Web3 | None = None) -> xDai:
409
+ return get_total_balance(
410
+ address=api_keys.bet_from_address, web3=web3, sum_xdai=True, sum_wxdai=True
411
+ )
412
+
413
+ @staticmethod
414
+ def verify_operational_balance(api_keys: APIKeys) -> bool:
415
+ return get_total_balance(
416
+ api_keys.public_key, # Use `public_key`, not `bet_from_address` because transaction costs are paid from the EOA wallet.
417
+ sum_wxdai=False,
418
+ ) > xdai_type(0.001)
419
+
420
+ def store_prediction(
421
+ self, processed_market: ProcessedMarket | None, keys: APIKeys
422
+ ) -> None:
423
+ """On Omen, we have to store predictions along with trades, see `store_trades`."""
424
+
425
+ def store_trades(
426
+ self, traded_market: ProcessedTradedMarket | None, keys: APIKeys
427
+ ) -> None:
428
+ if traded_market is None:
429
+ logger.warning(f"No prediction for market {self.id}, not storing anything.")
430
+ return
431
+
432
+ reasoning = (
433
+ traded_market.answer.reasoning if traded_market.answer.reasoning else ""
434
+ )
435
+
436
+ ipfs_hash_decoded = HexBytes(HASH_ZERO)
437
+ if keys.enable_ipfs_upload:
438
+ logger.info("Storing prediction on IPFS.")
439
+ ipfs_hash = IPFSHandler(keys).store_agent_result(
440
+ IPFSAgentResult(reasoning=reasoning)
441
+ )
442
+ ipfs_hash_decoded = ipfscidv0_to_byte32(ipfs_hash)
443
+
444
+ tx_hashes = [
445
+ HexBytes(HexStr(i.id)) for i in traded_market.trades if i.id is not None
446
+ ]
447
+ prediction = ContractPrediction(
448
+ publisher=keys.public_key,
449
+ ipfs_hash=ipfs_hash_decoded,
450
+ tx_hashes=tx_hashes,
451
+ estimated_probability_bps=int(traded_market.answer.p_yes * 10000),
452
+ )
453
+ tx_receipt = OmenAgentResultMappingContract().add_prediction(
454
+ api_keys=keys,
455
+ market_address=Web3.to_checksum_address(self.id),
456
+ prediction=prediction,
457
+ )
458
+ logger.info(
459
+ f"Added prediction to market {self.id}. - receipt {tx_receipt['transactionHash'].hex()}."
460
+ )
461
+
395
462
  @staticmethod
396
463
  def get_bets_made_since(
397
464
  better_address: ChecksumAddress, start_time: DatetimeUTC
@@ -1222,13 +1289,12 @@ def get_binary_market_p_yes_history(market: OmenAgentMarket) -> list[Probability
1222
1289
  return history
1223
1290
 
1224
1291
 
1225
- def is_minimum_required_balance(
1292
+ def get_total_balance(
1226
1293
  address: ChecksumAddress,
1227
- min_required_balance: xDai,
1228
1294
  web3: Web3 | None = None,
1229
1295
  sum_xdai: bool = True,
1230
1296
  sum_wxdai: bool = True,
1231
- ) -> bool:
1297
+ ) -> xDai:
1232
1298
  """
1233
1299
  Checks if the total balance of xDai and wxDai in the wallet is above the minimum required balance.
1234
1300
  """
@@ -1239,7 +1305,7 @@ def is_minimum_required_balance(
1239
1305
  total_balance += current_balances.xdai
1240
1306
  if sum_wxdai:
1241
1307
  total_balance += current_balances.wxdai
1242
- return total_balance >= min_required_balance
1308
+ return xdai_type(total_balance)
1243
1309
 
1244
1310
 
1245
1311
  def withdraw_wxdai_to_xdai_to_keep_balance(
@@ -24,7 +24,7 @@ QUESTION_IS_INVALID_PROMPT = """Main signs about an invalid question (sometimes
24
24
  - The violent event can be caused by a single conscious being.
25
25
  - The violent event is done illegally.
26
26
  - The market should not directly incentivize immoral violent (such as murder, rape or unjust imprisonment) actions which could likely be performed by any participant.
27
- - Invalid: Will Donald Trump be alive on the 01/12/2021? (Anyone could bet on No and kill him for a guaranteed profit. Anyone could bet on Yes to effectively put a bounty on his head).
27
+ - Invalid: Will Donald Trump be alive on the 01/12/2021? (Anyone could bet on "No" and kill him for a guaranteed profit. Anyone could bet on "Yes" to effectively put a bounty on his head).
28
28
  - Invalid: Will Hera be a victim of swatting in 2020? (Anyone could falsely call the emergency services on him in order to win the bet)
29
29
  - This does not prevent markets:
30
30
  - Whose topics are violent events not caused by conscious beings.
@@ -35,9 +35,10 @@ QUESTION_IS_INVALID_PROMPT = """Main signs about an invalid question (sometimes
35
35
  - Valid: Will the US be engaged in a military conflict with a UN member state in 2021? (It’s unlikely for the US to declare war in order to win a bet on this market).
36
36
  - Valid: Will Derek Chauvin go to jail for the murder of George Flyod? (It’s unlikely that the jurors would collude to make a wrong verdict in order to win this market).
37
37
  - Questions with relative dates will resolve as invalid. Dates must be stated in absolute terms, not relative depending on the current time.
38
- - Invalid: Who will be the president of the United States in 6 months? (in 6 months depends on the current time).
38
+ - Invalid: Who will be the president of the United States in 6 months? ("in 6 months depends on the current time").
39
+ - Invalid: In the next 14 days, will Gnosis Chain gain another 1M users? ("in the next 14 days depends on the current time").
39
40
  - Questions about moral values and not facts will be resolved as invalid.
40
- - Invalid: Is it ethical to eat meat?”.
41
+ - Invalid: "Is it ethical to eat meat?".
41
42
 
42
43
  Follow a chain of thought to evaluate if the question is invalid:
43
44
 
@@ -33,6 +33,11 @@ def tavily_search(
33
33
 
34
34
  Argument default values are different from the original method, to return everything by default, because it can be handy in the future and it doesn't increase the costs.
35
35
  """
36
+ if topic == "news" and days is None:
37
+ raise ValueError("When topic is 'news', days must be an integer")
38
+ if topic == "general" and days is not None:
39
+ raise ValueError("When topic is 'general', days must be None")
40
+
36
41
  if tavily_storage and (
37
42
  response_parsed := tavily_storage.find(
38
43
  query=query,
@@ -103,11 +108,15 @@ def _tavily_search(
103
108
  tavily = TavilyClient(
104
109
  api_key=(api_keys or APIKeys()).tavily_api_key.get_secret_value()
105
110
  )
111
+
112
+ # Optional `days` arg can only be specified if not None, otherwise Tavily
113
+ # will throw an error
114
+ kwargs = {"days": days} if days else {}
115
+
106
116
  response: dict[str, t.Any] = tavily.search(
107
117
  query=query,
108
118
  search_depth=search_depth,
109
119
  topic=topic,
110
- days=days,
111
120
  max_results=max_results,
112
121
  include_domains=include_domains,
113
122
  exclude_domains=exclude_domains,
@@ -115,6 +124,7 @@ def _tavily_search(
115
124
  include_raw_content=include_raw_content,
116
125
  include_images=include_images,
117
126
  use_cache=use_cache,
127
+ **kwargs,
118
128
  )
119
129
  return response
120
130
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: prediction-market-agent-tooling
3
- Version: 0.53.0
3
+ Version: 0.55.0
4
4
  Summary: Tools to benchmark, deploy and monitor prediction market agents.
5
5
  Author: Gnosis
6
6
  Requires-Python: >=3.10,<3.12
@@ -44,7 +44,7 @@ Requires-Dist: sqlmodel (>=0.0.22,<0.0.23)
44
44
  Requires-Dist: streamlit (>=1.31.0,<2.0.0)
45
45
  Requires-Dist: subgrounds (>=1.9.1,<2.0.0)
46
46
  Requires-Dist: tabulate (>=0.9.0,<0.10.0)
47
- Requires-Dist: tavily-python (>=0.3.9,<0.4.0)
47
+ Requires-Dist: tavily-python (>=0.5.0,<0.6.0)
48
48
  Requires-Dist: tqdm (>=4.66.2,<5.0.0)
49
49
  Requires-Dist: typer (>=0.9.0,<1.0.0)
50
50
  Requires-Dist: types-python-dateutil (>=2.9.0.20240906,<3.0.0.0)
@@ -17,9 +17,9 @@ prediction_market_agent_tooling/benchmark/agents.py,sha256=B1-uWdyeN4GGKMWGK_-Cc
17
17
  prediction_market_agent_tooling/benchmark/benchmark.py,sha256=MqTiaaJ3cYiOLUVR7OyImLWxcEya3Rl5JyFYW-K0lwM,17097
18
18
  prediction_market_agent_tooling/benchmark/utils.py,sha256=D0MfUkVZllmvcU0VOurk9tcKT7JTtwwOp-63zuCBVuc,2880
19
19
  prediction_market_agent_tooling/config.py,sha256=WC30Nr16RGueTafA9i67OIB-6KDHZRryhiLPzebg9_I,6740
20
- prediction_market_agent_tooling/deploy/agent.py,sha256=3yookl4xiLLcIzvE5h-Rf6_NmBRZuKDsJu9vFJaSCtk,22294
20
+ prediction_market_agent_tooling/deploy/agent.py,sha256=1a1VPaCA77MhK9wQwX1MjEycareP_NYfsm73YFXwyxY,22222
21
21
  prediction_market_agent_tooling/deploy/agent_example.py,sha256=dIIdZashExWk9tOdyDjw87AuUcGyM7jYxNChYrVK2dM,1001
22
- prediction_market_agent_tooling/deploy/betting_strategy.py,sha256=6PceKCNa4CM8ws033JFoCDb2Xm9OmtMjCtdSWXD5BpQ,12495
22
+ prediction_market_agent_tooling/deploy/betting_strategy.py,sha256=kMrIE3wMv_IB6nJd_1DmDXDkEZhsXFOgyTd7JZ0gqHI,13068
23
23
  prediction_market_agent_tooling/deploy/constants.py,sha256=M5ty8URipYMGe_G-RzxRydK3AFL6CyvmqCraJUrLBnE,82
24
24
  prediction_market_agent_tooling/deploy/gcp/deploy.py,sha256=CYUgnfy-9XVk04kkxA_5yp0GE9Mw5caYqlFUZQ2j3ks,3739
25
25
  prediction_market_agent_tooling/deploy/gcp/kubernetes_models.py,sha256=OsPboCFGiZKsvGyntGZHwdqPlLTthITkNF5rJFvGgU8,2582
@@ -30,22 +30,22 @@ prediction_market_agent_tooling/jobs/jobs.py,sha256=I07yh0GJ-xhlvQaOUQB8xlSnihhc
30
30
  prediction_market_agent_tooling/jobs/jobs_models.py,sha256=I5uBTHJ2S1Wi3H4jDxxU7nsswSIP9r3BevHmljLh5Pg,1370
31
31
  prediction_market_agent_tooling/jobs/omen/omen_jobs.py,sha256=I2_vGrEJj1reSI8M377ab5QCsYNp_l4l4QeYEmDBkFM,3989
32
32
  prediction_market_agent_tooling/loggers.py,sha256=Am6HHXRNO545BO3l7Ue9Wb2TkYE1OK8KKhGbI3XypVU,3751
33
- prediction_market_agent_tooling/markets/agent_market.py,sha256=09Guz5y5Uq0K8nKFmy8SqxNfCYJX3auyxWLzIuOek2I,11119
33
+ prediction_market_agent_tooling/markets/agent_market.py,sha256=OgB6bvDGfTAxbh6cDGD3XFO0iy0MAaOQvXEP6nw8xW8,12817
34
34
  prediction_market_agent_tooling/markets/categorize.py,sha256=jsoHWvZk9pU6n17oWSCcCxNNYVwlb_NXsZxKRI7vmsk,1301
35
35
  prediction_market_agent_tooling/markets/data_models.py,sha256=jMqrSFO_w2z-5N3PFVgZqTHdVdkzSDhhzky2lHsGGKA,3621
36
36
  prediction_market_agent_tooling/markets/manifold/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
37
37
  prediction_market_agent_tooling/markets/manifold/api.py,sha256=Fd0HYnstvvHO6AZkp1xiRlvCwQUc8kLR8DAj6PAZu0s,7297
38
38
  prediction_market_agent_tooling/markets/manifold/data_models.py,sha256=ylXIEHymx2RcCdOVUpKP4BcTqbzLu_Fd5gV1TGBntVk,6099
39
- prediction_market_agent_tooling/markets/manifold/manifold.py,sha256=IeVS3Suifryym-aLFDNEyyhdT3Gg03l4O39-PDpXmE8,4895
39
+ prediction_market_agent_tooling/markets/manifold/manifold.py,sha256=qemQIwuFg4yf6egGWFp9lWpz1lXr02QiBeZ2akcT6II,5026
40
40
  prediction_market_agent_tooling/markets/manifold/utils.py,sha256=cPPFWXm3vCYH1jy7_ctJZuQH9ZDaPL4_AgAYzGWkoow,513
41
41
  prediction_market_agent_tooling/markets/market_fees.py,sha256=Q64T9uaJx0Vllt0BkrPmpMEz53ra-hMVY8Czi7CEP7s,1227
42
- prediction_market_agent_tooling/markets/markets.py,sha256=_3nV9QTT48G2oJ2egkuWA1UzrTOGY6x3mXqIRgDaVIo,3245
42
+ prediction_market_agent_tooling/markets/markets.py,sha256=mwubc567OIlA32YKqlIdTloYV8FGJia9gPv0wE0xUEA,3368
43
43
  prediction_market_agent_tooling/markets/metaculus/api.py,sha256=4TRPGytQQbSdf42DCg2M_JWYPAuNjqZ3eBqaQBLkNks,2736
44
- prediction_market_agent_tooling/markets/metaculus/data_models.py,sha256=2wDZ0BmK9O5Lud-q-FCzgW0tsK9GxMU0rUMlcPxSS04,3184
45
- prediction_market_agent_tooling/markets/metaculus/metaculus.py,sha256=d5tHhekLfPGO6CfmWWJQWpJu5HyoAWBM2aGSR874Cms,3695
44
+ prediction_market_agent_tooling/markets/metaculus/data_models.py,sha256=Suxa7xELdYuFNKqvGvFh8qyfVtAg79E-vaQ6dqNZOtA,3261
45
+ prediction_market_agent_tooling/markets/metaculus/metaculus.py,sha256=E_TUf5q73lWzdMp40Ne-3w4MjEd7AHcaif4pvFh9FMU,4360
46
46
  prediction_market_agent_tooling/markets/omen/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
47
47
  prediction_market_agent_tooling/markets/omen/data_models.py,sha256=nCjsc-ylIzQOCK_1BW-5NoYrS-NIXz2Hg9N1-IqhhC8,27516
48
- prediction_market_agent_tooling/markets/omen/omen.py,sha256=3BF3oH78K_puwGktLkkOIxsa3G61Jb2zx6LZ_kjr3Hk,48638
48
+ prediction_market_agent_tooling/markets/omen/omen.py,sha256=LqNZjngo6LoktKecfYmGmJJ9D5rj-s0Poy4x4_GZfp0,51116
49
49
  prediction_market_agent_tooling/markets/omen/omen_contracts.py,sha256=Zq7SncCq-hvpgXKsVruGBGCn1OhKZTe7r1qLdCTrT2w,28297
50
50
  prediction_market_agent_tooling/markets/omen/omen_resolving.py,sha256=iDWdjICGkt968exwCjY-6nsnQyrrNAg3YjnDdP430GQ,9415
51
51
  prediction_market_agent_tooling/markets/omen/omen_subgraph_handler.py,sha256=zQH3iu0SVH1RmE-W3NMEpcKVMILXJYxMhL6w1wh5RUo,37348
@@ -79,7 +79,7 @@ prediction_market_agent_tooling/tools/httpx_cached_client.py,sha256=0-N1r0zcGKlY
79
79
  prediction_market_agent_tooling/tools/image_gen/image_gen.py,sha256=HzRwBx62hOXBOmrtpkXaP9Qq1Ku03uUGdREocyjLQ_k,1266
80
80
  prediction_market_agent_tooling/tools/image_gen/market_thumbnail_gen.py,sha256=8A3U2uxsCsOfLjru-6R_PPIAuiKY4qFkWp_GSBPV6-s,1280
81
81
  prediction_market_agent_tooling/tools/ipfs/ipfs_handler.py,sha256=CTTMfTvs_8PH4kAtlQby2aeEKwgpmxtuGbd4oYIdJ2A,1201
82
- prediction_market_agent_tooling/tools/is_invalid.py,sha256=Lc5fWB4fmx7tFvRakmUOzo0Oq5EizorddZ2xjesEopY,4984
82
+ prediction_market_agent_tooling/tools/is_invalid.py,sha256=VjjOCrkt6S8ytOg_0s2gUL8IKX2muWq1QLIZX0MPMxY,5094
83
83
  prediction_market_agent_tooling/tools/is_predictable.py,sha256=NIoR2bTNMmADcyNY2aKNMWkiDw7Z_9kZMcFXEdyewy4,6771
84
84
  prediction_market_agent_tooling/tools/langfuse_.py,sha256=jI_4ROxqo41CCnWGS1vN_AeDVhRzLMaQLxH3kxDu3L8,1153
85
85
  prediction_market_agent_tooling/tools/langfuse_client_utils.py,sha256=B0PhAQyviFnVbtOCYMxYmcCn66cu9nbqAOIAZcdgiRI,5771
@@ -89,12 +89,12 @@ prediction_market_agent_tooling/tools/safe.py,sha256=h0xOO0eNtitClf0fPkn-0oTc6A_
89
89
  prediction_market_agent_tooling/tools/singleton.py,sha256=CiIELUiI-OeS7U7eeHEt0rnVhtQGzwoUdAgn_7u_GBM,729
90
90
  prediction_market_agent_tooling/tools/streamlit_user_login.py,sha256=NXEqfjT9Lc9QtliwSGRASIz1opjQ7Btme43H4qJbzgE,3010
91
91
  prediction_market_agent_tooling/tools/tavily/tavily_models.py,sha256=Rz4tZzwCRzPaq49SFT33SCRQrqHXtqWdD9ajb2tGCWc,2723
92
- prediction_market_agent_tooling/tools/tavily/tavily_search.py,sha256=MK_ozeQbJ014HGiKFPDScjFYq0OGcjY1KPgc9A6qO0M,4511
92
+ prediction_market_agent_tooling/tools/tavily/tavily_search.py,sha256=inGgWo_TFLA7Q0agYx2MYdEsh659oIdovQMniiG_Q20,4895
93
93
  prediction_market_agent_tooling/tools/tavily/tavily_storage.py,sha256=t-tZzbCzBBdFedRZDuVBn3A3mIDX8Z5wza6SxWswu_E,4093
94
94
  prediction_market_agent_tooling/tools/utils.py,sha256=W-9SqeCKd51BYMRhDjYPQ7lfNO_zE9EvYpmu2r5WXGA,7163
95
95
  prediction_market_agent_tooling/tools/web3_utils.py,sha256=dkcjG-LtuaWRh7WEMzRGmZ5B5rsxZTlliFOI6fj-EJ8,11842
96
- prediction_market_agent_tooling-0.53.0.dist-info/LICENSE,sha256=6or154nLLU6bELzjh0mCreFjt0m2v72zLi3yHE0QbeE,7650
97
- prediction_market_agent_tooling-0.53.0.dist-info/METADATA,sha256=RrGcyPVn2Gs6Xx9ezoDYzqUMAQfDZVlT63_Uq22GjSc,8056
98
- prediction_market_agent_tooling-0.53.0.dist-info/WHEEL,sha256=Nq82e9rUAnEjt98J6MlVmMCZb-t9cYE2Ir1kpBmnWfs,88
99
- prediction_market_agent_tooling-0.53.0.dist-info/entry_points.txt,sha256=m8PukHbeH5g0IAAmOf_1Ahm-sGAMdhSSRQmwtpmi2s8,81
100
- prediction_market_agent_tooling-0.53.0.dist-info/RECORD,,
96
+ prediction_market_agent_tooling-0.55.0.dist-info/LICENSE,sha256=6or154nLLU6bELzjh0mCreFjt0m2v72zLi3yHE0QbeE,7650
97
+ prediction_market_agent_tooling-0.55.0.dist-info/METADATA,sha256=2a-TbhiR5XyBGScQZDd1pV1vzC-LBWt7zFa6WorpmD8,8056
98
+ prediction_market_agent_tooling-0.55.0.dist-info/WHEEL,sha256=Nq82e9rUAnEjt98J6MlVmMCZb-t9cYE2Ir1kpBmnWfs,88
99
+ prediction_market_agent_tooling-0.55.0.dist-info/entry_points.txt,sha256=m8PukHbeH5g0IAAmOf_1Ahm-sGAMdhSSRQmwtpmi2s8,81
100
+ prediction_market_agent_tooling-0.55.0.dist-info/RECORD,,