prediction-market-agent-tooling 0.48.10__tar.gz → 0.48.12__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (85) hide show
  1. {prediction_market_agent_tooling-0.48.10 → prediction_market_agent_tooling-0.48.12}/PKG-INFO +1 -1
  2. {prediction_market_agent_tooling-0.48.10 → prediction_market_agent_tooling-0.48.12}/prediction_market_agent_tooling/deploy/agent.py +35 -33
  3. prediction_market_agent_tooling-0.48.12/prediction_market_agent_tooling/deploy/betting_strategy.py +187 -0
  4. {prediction_market_agent_tooling-0.48.10 → prediction_market_agent_tooling-0.48.12}/prediction_market_agent_tooling/markets/agent_market.py +45 -1
  5. {prediction_market_agent_tooling-0.48.10 → prediction_market_agent_tooling-0.48.12}/prediction_market_agent_tooling/markets/data_models.py +11 -0
  6. {prediction_market_agent_tooling-0.48.10 → prediction_market_agent_tooling-0.48.12}/prediction_market_agent_tooling/markets/manifold/data_models.py +6 -0
  7. {prediction_market_agent_tooling-0.48.10 → prediction_market_agent_tooling-0.48.12}/prediction_market_agent_tooling/markets/manifold/manifold.py +3 -0
  8. {prediction_market_agent_tooling-0.48.10 → prediction_market_agent_tooling-0.48.12}/prediction_market_agent_tooling/markets/metaculus/metaculus.py +1 -0
  9. {prediction_market_agent_tooling-0.48.10 → prediction_market_agent_tooling-0.48.12}/prediction_market_agent_tooling/markets/omen/data_models.py +4 -0
  10. {prediction_market_agent_tooling-0.48.10 → prediction_market_agent_tooling-0.48.12}/prediction_market_agent_tooling/markets/omen/omen.py +69 -6
  11. {prediction_market_agent_tooling-0.48.10 → prediction_market_agent_tooling-0.48.12}/prediction_market_agent_tooling/markets/omen/omen_subgraph_handler.py +18 -8
  12. {prediction_market_agent_tooling-0.48.10 → prediction_market_agent_tooling-0.48.12}/prediction_market_agent_tooling/markets/polymarket/polymarket.py +1 -0
  13. {prediction_market_agent_tooling-0.48.10 → prediction_market_agent_tooling-0.48.12}/prediction_market_agent_tooling/tools/contract.py +6 -1
  14. {prediction_market_agent_tooling-0.48.10 → prediction_market_agent_tooling-0.48.12}/prediction_market_agent_tooling/tools/tavily_storage/tavily_models.py +50 -3
  15. {prediction_market_agent_tooling-0.48.10 → prediction_market_agent_tooling-0.48.12}/prediction_market_agent_tooling/tools/tavily_storage/tavily_storage.py +15 -0
  16. {prediction_market_agent_tooling-0.48.10 → prediction_market_agent_tooling-0.48.12}/prediction_market_agent_tooling/tools/web3_utils.py +39 -8
  17. {prediction_market_agent_tooling-0.48.10 → prediction_market_agent_tooling-0.48.12}/pyproject.toml +1 -1
  18. prediction_market_agent_tooling-0.48.10/prediction_market_agent_tooling/deploy/betting_strategy.py +0 -62
  19. {prediction_market_agent_tooling-0.48.10 → prediction_market_agent_tooling-0.48.12}/LICENSE +0 -0
  20. {prediction_market_agent_tooling-0.48.10 → prediction_market_agent_tooling-0.48.12}/README.md +0 -0
  21. {prediction_market_agent_tooling-0.48.10 → prediction_market_agent_tooling-0.48.12}/prediction_market_agent_tooling/abis/depositablewrapper_erc20.abi.json +0 -0
  22. {prediction_market_agent_tooling-0.48.10 → prediction_market_agent_tooling-0.48.12}/prediction_market_agent_tooling/abis/erc20.abi.json +0 -0
  23. {prediction_market_agent_tooling-0.48.10 → prediction_market_agent_tooling-0.48.12}/prediction_market_agent_tooling/abis/erc4626.abi.json +0 -0
  24. {prediction_market_agent_tooling-0.48.10 → prediction_market_agent_tooling-0.48.12}/prediction_market_agent_tooling/abis/omen_dxdao.abi.json +0 -0
  25. {prediction_market_agent_tooling-0.48.10 → prediction_market_agent_tooling-0.48.12}/prediction_market_agent_tooling/abis/omen_fpmm.abi.json +0 -0
  26. {prediction_market_agent_tooling-0.48.10 → prediction_market_agent_tooling-0.48.12}/prediction_market_agent_tooling/abis/omen_fpmm_conditionaltokens.abi.json +0 -0
  27. {prediction_market_agent_tooling-0.48.10 → prediction_market_agent_tooling-0.48.12}/prediction_market_agent_tooling/abis/omen_fpmm_factory.abi.json +0 -0
  28. {prediction_market_agent_tooling-0.48.10 → prediction_market_agent_tooling-0.48.12}/prediction_market_agent_tooling/abis/omen_kleros.abi.json +0 -0
  29. {prediction_market_agent_tooling-0.48.10 → prediction_market_agent_tooling-0.48.12}/prediction_market_agent_tooling/abis/omen_oracle.abi.json +0 -0
  30. {prediction_market_agent_tooling-0.48.10 → prediction_market_agent_tooling-0.48.12}/prediction_market_agent_tooling/abis/omen_realitio.abi.json +0 -0
  31. {prediction_market_agent_tooling-0.48.10 → prediction_market_agent_tooling-0.48.12}/prediction_market_agent_tooling/abis/omen_thumbnailmapping.abi.json +0 -0
  32. {prediction_market_agent_tooling-0.48.10 → prediction_market_agent_tooling-0.48.12}/prediction_market_agent_tooling/abis/proxy.abi.json +0 -0
  33. {prediction_market_agent_tooling-0.48.10 → prediction_market_agent_tooling-0.48.12}/prediction_market_agent_tooling/benchmark/__init__.py +0 -0
  34. {prediction_market_agent_tooling-0.48.10 → prediction_market_agent_tooling-0.48.12}/prediction_market_agent_tooling/benchmark/agents.py +0 -0
  35. {prediction_market_agent_tooling-0.48.10 → prediction_market_agent_tooling-0.48.12}/prediction_market_agent_tooling/benchmark/benchmark.py +0 -0
  36. {prediction_market_agent_tooling-0.48.10 → prediction_market_agent_tooling-0.48.12}/prediction_market_agent_tooling/benchmark/utils.py +0 -0
  37. {prediction_market_agent_tooling-0.48.10 → prediction_market_agent_tooling-0.48.12}/prediction_market_agent_tooling/config.py +0 -0
  38. {prediction_market_agent_tooling-0.48.10 → prediction_market_agent_tooling-0.48.12}/prediction_market_agent_tooling/deploy/agent_example.py +0 -0
  39. {prediction_market_agent_tooling-0.48.10 → prediction_market_agent_tooling-0.48.12}/prediction_market_agent_tooling/deploy/constants.py +0 -0
  40. {prediction_market_agent_tooling-0.48.10 → prediction_market_agent_tooling-0.48.12}/prediction_market_agent_tooling/deploy/gcp/deploy.py +0 -0
  41. {prediction_market_agent_tooling-0.48.10 → prediction_market_agent_tooling-0.48.12}/prediction_market_agent_tooling/deploy/gcp/kubernetes_models.py +0 -0
  42. {prediction_market_agent_tooling-0.48.10 → prediction_market_agent_tooling-0.48.12}/prediction_market_agent_tooling/deploy/gcp/utils.py +0 -0
  43. {prediction_market_agent_tooling-0.48.10 → prediction_market_agent_tooling-0.48.12}/prediction_market_agent_tooling/gtypes.py +0 -0
  44. {prediction_market_agent_tooling-0.48.10 → prediction_market_agent_tooling-0.48.12}/prediction_market_agent_tooling/loggers.py +0 -0
  45. {prediction_market_agent_tooling-0.48.10 → prediction_market_agent_tooling-0.48.12}/prediction_market_agent_tooling/markets/categorize.py +0 -0
  46. {prediction_market_agent_tooling-0.48.10 → prediction_market_agent_tooling-0.48.12}/prediction_market_agent_tooling/markets/manifold/__init__.py +0 -0
  47. {prediction_market_agent_tooling-0.48.10 → prediction_market_agent_tooling-0.48.12}/prediction_market_agent_tooling/markets/manifold/api.py +0 -0
  48. {prediction_market_agent_tooling-0.48.10 → prediction_market_agent_tooling-0.48.12}/prediction_market_agent_tooling/markets/manifold/utils.py +0 -0
  49. {prediction_market_agent_tooling-0.48.10 → prediction_market_agent_tooling-0.48.12}/prediction_market_agent_tooling/markets/markets.py +0 -0
  50. {prediction_market_agent_tooling-0.48.10 → prediction_market_agent_tooling-0.48.12}/prediction_market_agent_tooling/markets/metaculus/api.py +0 -0
  51. {prediction_market_agent_tooling-0.48.10 → prediction_market_agent_tooling-0.48.12}/prediction_market_agent_tooling/markets/metaculus/data_models.py +0 -0
  52. {prediction_market_agent_tooling-0.48.10 → prediction_market_agent_tooling-0.48.12}/prediction_market_agent_tooling/markets/omen/__init__.py +0 -0
  53. {prediction_market_agent_tooling-0.48.10 → prediction_market_agent_tooling-0.48.12}/prediction_market_agent_tooling/markets/omen/omen_contracts.py +0 -0
  54. {prediction_market_agent_tooling-0.48.10 → prediction_market_agent_tooling-0.48.12}/prediction_market_agent_tooling/markets/omen/omen_resolving.py +0 -0
  55. {prediction_market_agent_tooling-0.48.10 → prediction_market_agent_tooling-0.48.12}/prediction_market_agent_tooling/markets/polymarket/api.py +0 -0
  56. {prediction_market_agent_tooling-0.48.10 → prediction_market_agent_tooling-0.48.12}/prediction_market_agent_tooling/markets/polymarket/data_models.py +0 -0
  57. {prediction_market_agent_tooling-0.48.10 → prediction_market_agent_tooling-0.48.12}/prediction_market_agent_tooling/markets/polymarket/data_models_web.py +0 -0
  58. {prediction_market_agent_tooling-0.48.10 → prediction_market_agent_tooling-0.48.12}/prediction_market_agent_tooling/markets/polymarket/utils.py +0 -0
  59. {prediction_market_agent_tooling-0.48.10 → prediction_market_agent_tooling-0.48.12}/prediction_market_agent_tooling/monitor/markets/manifold.py +0 -0
  60. {prediction_market_agent_tooling-0.48.10 → prediction_market_agent_tooling-0.48.12}/prediction_market_agent_tooling/monitor/markets/metaculus.py +0 -0
  61. {prediction_market_agent_tooling-0.48.10 → prediction_market_agent_tooling-0.48.12}/prediction_market_agent_tooling/monitor/markets/omen.py +0 -0
  62. {prediction_market_agent_tooling-0.48.10 → prediction_market_agent_tooling-0.48.12}/prediction_market_agent_tooling/monitor/markets/polymarket.py +0 -0
  63. {prediction_market_agent_tooling-0.48.10 → prediction_market_agent_tooling-0.48.12}/prediction_market_agent_tooling/monitor/monitor.py +0 -0
  64. {prediction_market_agent_tooling-0.48.10 → prediction_market_agent_tooling-0.48.12}/prediction_market_agent_tooling/monitor/monitor_app.py +0 -0
  65. {prediction_market_agent_tooling-0.48.10 → prediction_market_agent_tooling-0.48.12}/prediction_market_agent_tooling/monitor/monitor_settings.py +0 -0
  66. {prediction_market_agent_tooling-0.48.10 → prediction_market_agent_tooling-0.48.12}/prediction_market_agent_tooling/py.typed +0 -0
  67. {prediction_market_agent_tooling-0.48.10 → prediction_market_agent_tooling-0.48.12}/prediction_market_agent_tooling/tools/balances.py +0 -0
  68. {prediction_market_agent_tooling-0.48.10 → prediction_market_agent_tooling-0.48.12}/prediction_market_agent_tooling/tools/betting_strategies/kelly_criterion.py +0 -0
  69. {prediction_market_agent_tooling-0.48.10 → prediction_market_agent_tooling-0.48.12}/prediction_market_agent_tooling/tools/betting_strategies/market_moving.py +0 -0
  70. {prediction_market_agent_tooling-0.48.10 → prediction_market_agent_tooling-0.48.12}/prediction_market_agent_tooling/tools/betting_strategies/minimum_bet_to_win.py +0 -0
  71. {prediction_market_agent_tooling-0.48.10 → prediction_market_agent_tooling-0.48.12}/prediction_market_agent_tooling/tools/betting_strategies/stretch_bet_between.py +0 -0
  72. {prediction_market_agent_tooling-0.48.10 → prediction_market_agent_tooling-0.48.12}/prediction_market_agent_tooling/tools/cache.py +0 -0
  73. {prediction_market_agent_tooling-0.48.10 → prediction_market_agent_tooling-0.48.12}/prediction_market_agent_tooling/tools/costs.py +0 -0
  74. {prediction_market_agent_tooling-0.48.10 → prediction_market_agent_tooling-0.48.12}/prediction_market_agent_tooling/tools/gnosis_rpc.py +0 -0
  75. {prediction_market_agent_tooling-0.48.10 → prediction_market_agent_tooling-0.48.12}/prediction_market_agent_tooling/tools/google.py +0 -0
  76. {prediction_market_agent_tooling-0.48.10 → prediction_market_agent_tooling-0.48.12}/prediction_market_agent_tooling/tools/hexbytes_custom.py +0 -0
  77. {prediction_market_agent_tooling-0.48.10 → prediction_market_agent_tooling-0.48.12}/prediction_market_agent_tooling/tools/image_gen/image_gen.py +0 -0
  78. {prediction_market_agent_tooling-0.48.10 → prediction_market_agent_tooling-0.48.12}/prediction_market_agent_tooling/tools/image_gen/market_thumbnail_gen.py +0 -0
  79. {prediction_market_agent_tooling-0.48.10 → prediction_market_agent_tooling-0.48.12}/prediction_market_agent_tooling/tools/is_predictable.py +0 -0
  80. {prediction_market_agent_tooling-0.48.10 → prediction_market_agent_tooling-0.48.12}/prediction_market_agent_tooling/tools/langfuse_.py +0 -0
  81. {prediction_market_agent_tooling-0.48.10 → prediction_market_agent_tooling-0.48.12}/prediction_market_agent_tooling/tools/parallelism.py +0 -0
  82. {prediction_market_agent_tooling-0.48.10 → prediction_market_agent_tooling-0.48.12}/prediction_market_agent_tooling/tools/safe.py +0 -0
  83. {prediction_market_agent_tooling-0.48.10 → prediction_market_agent_tooling-0.48.12}/prediction_market_agent_tooling/tools/singleton.py +0 -0
  84. {prediction_market_agent_tooling-0.48.10 → prediction_market_agent_tooling-0.48.12}/prediction_market_agent_tooling/tools/streamlit_user_login.py +0 -0
  85. {prediction_market_agent_tooling-0.48.10 → prediction_market_agent_tooling-0.48.12}/prediction_market_agent_tooling/tools/utils.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: prediction-market-agent-tooling
3
- Version: 0.48.10
3
+ Version: 0.48.12
4
4
  Summary: Tools to benchmark, deploy and monitor prediction market agents.
5
5
  Author: Gnosis
6
6
  Requires-Python: >=3.10,<3.12
@@ -15,6 +15,7 @@ from prediction_market_agent_tooling.config import APIKeys
15
15
  from prediction_market_agent_tooling.deploy.betting_strategy import (
16
16
  BettingStrategy,
17
17
  MaxAccuracyBettingStrategy,
18
+ TradeType,
18
19
  )
19
20
  from prediction_market_agent_tooling.deploy.constants import (
20
21
  MARKET_TYPE_KEY,
@@ -37,10 +38,9 @@ from prediction_market_agent_tooling.markets.agent_market import (
37
38
  SortBy,
38
39
  )
39
40
  from prediction_market_agent_tooling.markets.data_models import (
40
- BetAmount,
41
+ Position,
41
42
  ProbabilisticAnswer,
42
- TokenAmount,
43
- TokenAmountAndDirection,
43
+ Trade,
44
44
  )
45
45
  from prediction_market_agent_tooling.markets.markets import (
46
46
  MarketType,
@@ -110,7 +110,7 @@ class OutOfFundsError(ValueError):
110
110
 
111
111
  class ProcessedMarket(BaseModel):
112
112
  answer: ProbabilisticAnswer
113
- amount: BetAmount
113
+ trades: list[Trade]
114
114
 
115
115
 
116
116
  class AnsweredEnum(str, Enum):
@@ -283,6 +283,7 @@ class DeployableTraderAgent(DeployableAgent):
283
283
  min_required_balance_to_operate: xDai | None = xdai_type(1)
284
284
  min_balance_to_keep_in_native_currency: xDai | None = xdai_type(0.1)
285
285
  strategy: BettingStrategy = MaxAccuracyBettingStrategy()
286
+ allow_opposite_bets: bool = False
286
287
 
287
288
  def __init__(
288
289
  self,
@@ -298,8 +299,8 @@ class DeployableTraderAgent(DeployableAgent):
298
299
  self.have_bet_on_market_since = observe()(self.have_bet_on_market_since) # type: ignore[method-assign]
299
300
  self.verify_market = observe()(self.verify_market) # type: ignore[method-assign]
300
301
  self.answer_binary_market = observe()(self.answer_binary_market) # type: ignore[method-assign]
301
- self.calculate_bet_amount_and_direction = observe()(self.calculate_bet_amount_and_direction) # type: ignore[method-assign]
302
302
  self.process_market = observe()(self.process_market) # type: ignore[method-assign]
303
+ self.build_trades = observe()(self.build_trades) # type: ignore[method-assign]
303
304
 
304
305
  def update_langfuse_trace_by_market(
305
306
  self, market_type: MarketType, market: AgentMarket
@@ -314,18 +315,6 @@ class DeployableTraderAgent(DeployableAgent):
314
315
  },
315
316
  )
316
317
 
317
- def calculate_bet_amount_and_direction(
318
- self, answer: ProbabilisticAnswer, market: AgentMarket
319
- ) -> TokenAmountAndDirection:
320
- amount_and_direction = self.strategy.calculate_bet_amount_and_direction(
321
- answer, market
322
- )
323
- if amount_and_direction.currency != market.currency:
324
- raise ValueError(
325
- f"Currency mismatch. Strategy yields {amount_and_direction.currency}, market has currency {market.currency}"
326
- )
327
- return amount_and_direction
328
-
329
318
  def update_langfuse_trace_by_processed_market(
330
319
  self, market_type: MarketType, processed_market: ProcessedMarket | None
331
320
  ) -> None:
@@ -416,13 +405,26 @@ class DeployableTraderAgent(DeployableAgent):
416
405
  )
417
406
  return available_markets
418
407
 
408
+ def build_trades(
409
+ self,
410
+ market: AgentMarket,
411
+ answer: ProbabilisticAnswer,
412
+ existing_position: Position | None,
413
+ ) -> list[Trade]:
414
+ trades = self.strategy.calculate_trades(existing_position, answer, market)
415
+ BettingStrategy.assert_trades_currency_match_markets(market, trades)
416
+ return trades
417
+
419
418
  def before_process_market(
420
419
  self, market_type: MarketType, market: AgentMarket
421
420
  ) -> None:
422
421
  self.update_langfuse_trace_by_market(market_type, market)
423
422
 
424
423
  def process_market(
425
- self, market_type: MarketType, market: AgentMarket, verify_market: bool = True
424
+ self,
425
+ market_type: MarketType,
426
+ market: AgentMarket,
427
+ verify_market: bool = True,
426
428
  ) -> ProcessedMarket | None:
427
429
  self.before_process_market(market_type, market)
428
430
 
@@ -438,26 +440,26 @@ class DeployableTraderAgent(DeployableAgent):
438
440
  self.update_langfuse_trace_by_processed_market(market_type, None)
439
441
  return None
440
442
 
441
- amount_and_direction = self.calculate_bet_amount_and_direction(answer, market)
443
+ existing_position = market.get_position(user_id=APIKeys().bet_from_address)
444
+ trades = self.build_trades(
445
+ market=market, answer=answer, existing_position=existing_position
446
+ )
442
447
 
443
448
  if self.place_bet:
444
- logger.info(
445
- f"Placing bet on {market} with direction {amount_and_direction.direction} and amount {amount_and_direction.amount}"
446
- )
447
- market.place_bet(
448
- amount=TokenAmount(
449
- amount=amount_and_direction.amount,
450
- currency=amount_and_direction.currency,
451
- ),
452
- outcome=amount_and_direction.direction,
453
- )
449
+ for trade in trades:
450
+ logger.info(f"Executing trade {trade}")
451
+
452
+ match trade.trade_type:
453
+ case TradeType.BUY:
454
+ market.buy_tokens(outcome=trade.outcome, amount=trade.amount)
455
+ case TradeType.SELL:
456
+ market.sell_tokens(outcome=trade.outcome, amount=trade.amount)
457
+ case _:
458
+ raise ValueError(f"Unexpected trade type {trade.trade_type}.")
454
459
 
455
460
  self.after_process_market(market_type, market)
456
461
 
457
- processed_market = ProcessedMarket(
458
- answer=answer,
459
- amount=amount_and_direction,
460
- )
462
+ processed_market = ProcessedMarket(answer=answer, trades=trades)
461
463
  self.update_langfuse_trace_by_processed_market(market_type, processed_market)
462
464
 
463
465
  return processed_market
@@ -0,0 +1,187 @@
1
+ from abc import ABC, abstractmethod
2
+
3
+ from prediction_market_agent_tooling.markets.agent_market import AgentMarket
4
+ from prediction_market_agent_tooling.markets.data_models import (
5
+ Currency,
6
+ Position,
7
+ ProbabilisticAnswer,
8
+ TokenAmount,
9
+ Trade,
10
+ TradeType,
11
+ )
12
+ from prediction_market_agent_tooling.markets.omen.data_models import get_boolean_outcome
13
+ from prediction_market_agent_tooling.tools.betting_strategies.kelly_criterion import (
14
+ get_kelly_bet,
15
+ )
16
+
17
+
18
+ class BettingStrategy(ABC):
19
+ @abstractmethod
20
+ def calculate_trades(
21
+ self,
22
+ existing_position: Position | None,
23
+ answer: ProbabilisticAnswer,
24
+ market: AgentMarket,
25
+ ) -> list[Trade]:
26
+ pass
27
+
28
+ def build_zero_token_amount(self, currency: Currency) -> TokenAmount:
29
+ return TokenAmount(amount=0, currency=currency)
30
+
31
+ @abstractmethod
32
+ def adjust_bet_amount(
33
+ self, existing_position: Position | None, market: AgentMarket
34
+ ) -> float:
35
+ pass
36
+
37
+ @staticmethod
38
+ def assert_trades_currency_match_markets(
39
+ market: AgentMarket, trades: list[Trade]
40
+ ) -> None:
41
+ currencies_match = all([t.amount.currency == market.currency for t in trades])
42
+ if not currencies_match:
43
+ raise ValueError(
44
+ "Cannot handle trades with currencies that deviate from market's currency"
45
+ )
46
+
47
+ def _build_rebalance_trades_from_positions(
48
+ self,
49
+ existing_position: Position | None,
50
+ target_position: Position,
51
+ market: AgentMarket,
52
+ ) -> list[Trade]:
53
+ """
54
+ This helper method builds trades by rebalancing token allocations to each outcome.
55
+ For example, if we have an existing position with 10 tokens in outcome 0 and 5 in outcome 1,
56
+ and our target position is 20 tokens in outcome 0 and 0 in outcome 1, we would return these trades:
57
+ trades = [
58
+ Trade(outcome=0, amount=10, trade_type=TradeType.BUY),
59
+ Trade(outcome=1, amount=5, trade_type=TradeType.SELL)
60
+ ]
61
+ Note that we order the trades to first buy then sell, in order to minimally tilt the odds so that
62
+ sell price is higher.
63
+ """
64
+ trades = []
65
+ for outcome in [
66
+ market.get_outcome_str_from_bool(True),
67
+ market.get_outcome_str_from_bool(False),
68
+ ]:
69
+ outcome_bool = get_boolean_outcome(outcome)
70
+ prev_amount: TokenAmount = (
71
+ existing_position.amounts[outcome]
72
+ if existing_position and outcome in existing_position.amounts
73
+ else self.build_zero_token_amount(currency=market.currency)
74
+ )
75
+ new_amount: TokenAmount = target_position.amounts.get(
76
+ outcome, self.build_zero_token_amount(currency=market.currency)
77
+ )
78
+
79
+ if prev_amount.currency != new_amount.currency:
80
+ raise ValueError("Cannot handle positions with different currencies")
81
+ diff_amount = prev_amount.amount - new_amount.amount
82
+ if diff_amount == 0:
83
+ continue
84
+ trade_type = TradeType.SELL if diff_amount < 0 else TradeType.BUY
85
+ trade = Trade(
86
+ amount=TokenAmount(amount=abs(diff_amount), currency=market.currency),
87
+ outcome=outcome_bool,
88
+ trade_type=trade_type,
89
+ )
90
+
91
+ trades.append(trade)
92
+
93
+ # Sort inplace with SELL last
94
+ trades.sort(key=lambda t: t.trade_type == TradeType.SELL)
95
+ BettingStrategy.assert_trades_currency_match_markets(market, trades)
96
+ return trades
97
+
98
+
99
+ class MaxAccuracyBettingStrategy(BettingStrategy):
100
+ def adjust_bet_amount(
101
+ self, existing_position: Position | None, market: AgentMarket
102
+ ) -> float:
103
+ existing_position_total_amount = (
104
+ existing_position.total_amount.amount if existing_position else 0
105
+ )
106
+ bet_amount = (
107
+ market.get_tiny_bet_amount().amount
108
+ if self.bet_amount is None
109
+ else self.bet_amount
110
+ )
111
+ return bet_amount + existing_position_total_amount
112
+
113
+ def __init__(self, bet_amount: float | None = None):
114
+ self.bet_amount = bet_amount
115
+
116
+ def calculate_trades(
117
+ self,
118
+ existing_position: Position | None,
119
+ answer: ProbabilisticAnswer,
120
+ market: AgentMarket,
121
+ ) -> list[Trade]:
122
+ adjusted_bet_amount = self.adjust_bet_amount(existing_position, market)
123
+
124
+ direction = self.calculate_direction(market.current_p_yes, answer.p_yes)
125
+
126
+ amounts = {
127
+ market.get_outcome_str_from_bool(direction): TokenAmount(
128
+ amount=adjusted_bet_amount,
129
+ currency=market.currency,
130
+ ),
131
+ }
132
+ target_position = Position(market_id=market.id, amounts=amounts)
133
+ trades = self._build_rebalance_trades_from_positions(
134
+ existing_position, target_position, market=market
135
+ )
136
+ return trades
137
+
138
+ @staticmethod
139
+ def calculate_direction(market_p_yes: float, estimate_p_yes: float) -> bool:
140
+ return estimate_p_yes >= 0.5
141
+
142
+
143
+ class MaxExpectedValueBettingStrategy(MaxAccuracyBettingStrategy):
144
+ @staticmethod
145
+ def calculate_direction(market_p_yes: float, estimate_p_yes: float) -> bool:
146
+ # If estimate_p_yes >= market.current_p_yes, then bet TRUE, else bet FALSE.
147
+ # This is equivalent to saying EXPECTED_VALUE = (estimate_p_yes * num_tokens_obtained_by_betting_yes) -
148
+ # ((1 - estimate_p_yes) * num_tokens_obtained_by_betting_no) >= 0
149
+ return estimate_p_yes >= market_p_yes
150
+
151
+
152
+ class KellyBettingStrategy(BettingStrategy):
153
+ def __init__(self, max_bet_amount: float = 10):
154
+ self.max_bet_amount = max_bet_amount
155
+
156
+ def adjust_bet_amount(
157
+ self, existing_position: Position | None, market: AgentMarket
158
+ ) -> float:
159
+ existing_position_total_amount = (
160
+ existing_position.total_amount.amount if existing_position else 0
161
+ )
162
+ return self.max_bet_amount + existing_position_total_amount
163
+
164
+ def calculate_trades(
165
+ self,
166
+ existing_position: Position | None,
167
+ answer: ProbabilisticAnswer,
168
+ market: AgentMarket,
169
+ ) -> list[Trade]:
170
+ adjusted_bet_amount = self.adjust_bet_amount(existing_position, market)
171
+ kelly_bet = get_kelly_bet(
172
+ adjusted_bet_amount,
173
+ market.current_p_yes,
174
+ answer.p_yes,
175
+ answer.confidence,
176
+ )
177
+
178
+ amounts = {
179
+ market.get_outcome_str_from_bool(kelly_bet.direction): TokenAmount(
180
+ amount=kelly_bet.size, currency=market.currency
181
+ ),
182
+ }
183
+ target_position = Position(market_id=market.id, amounts=amounts)
184
+ trades = self._build_rebalance_trades_from_positions(
185
+ existing_position, target_position, market=market
186
+ )
187
+ return trades
@@ -4,9 +4,10 @@ from enum import Enum
4
4
 
5
5
  from eth_typing import ChecksumAddress
6
6
  from pydantic import BaseModel, field_validator
7
+ from pydantic_core.core_schema import FieldValidationInfo
7
8
 
8
9
  from prediction_market_agent_tooling.config import APIKeys
9
- from prediction_market_agent_tooling.gtypes import Probability
10
+ from prediction_market_agent_tooling.gtypes import OutcomeStr, Probability
10
11
  from prediction_market_agent_tooling.markets.data_models import (
11
12
  Bet,
12
13
  BetAmount,
@@ -49,6 +50,9 @@ class AgentMarket(BaseModel):
49
50
  question: str
50
51
  description: str | None
51
52
  outcomes: list[str]
53
+ outcome_token_pool: dict[
54
+ str, float
55
+ ] | None # Should be in currency of `currency` above.
52
56
  resolution: Resolution | None
53
57
  created_time: datetime | None
54
58
  close_time: datetime | None
@@ -63,6 +67,22 @@ class AgentMarket(BaseModel):
63
67
  add_utc_timezone_validator
64
68
  )
65
69
 
70
+ @field_validator("outcome_token_pool")
71
+ def validate_outcome_token_pool(
72
+ cls,
73
+ outcome_token_pool: dict[str, float] | None,
74
+ info: FieldValidationInfo,
75
+ ) -> dict[str, float] | None:
76
+ outcomes: list[str] = check_not_none(info.data.get("outcomes"))
77
+ if outcome_token_pool is not None:
78
+ outcome_keys = set(outcome_token_pool.keys())
79
+ expected_keys = set(outcomes)
80
+ if outcome_keys != expected_keys:
81
+ raise ValueError(
82
+ f"Keys of outcome_token_pool ({outcome_keys}) do not match outcomes ({expected_keys})."
83
+ )
84
+ return outcome_token_pool
85
+
66
86
  @property
67
87
  def current_p_no(self) -> Probability:
68
88
  return Probability(1 - self.current_p_yes)
@@ -133,10 +153,19 @@ class AgentMarket(BaseModel):
133
153
  def get_bet_amount(self, amount: float) -> BetAmount:
134
154
  return BetAmount(amount=amount, currency=self.currency)
135
155
 
156
+ @classmethod
157
+ def get_liquidatable_amount(cls) -> BetAmount:
158
+ tiny_amount = cls.get_tiny_bet_amount()
159
+ tiny_amount.amount /= 10
160
+ return tiny_amount
161
+
136
162
  @classmethod
137
163
  def get_tiny_bet_amount(cls) -> BetAmount:
138
164
  raise NotImplementedError("Subclasses must implement this method")
139
165
 
166
+ def liquidate_existing_positions(self, outcome: bool) -> None:
167
+ raise NotImplementedError("Subclasses must implement this method")
168
+
140
169
  def place_bet(self, outcome: bool, amount: BetAmount) -> None:
141
170
  raise NotImplementedError("Subclasses must implement this method")
142
171
 
@@ -190,6 +219,9 @@ class AgentMarket(BaseModel):
190
219
  def has_unsuccessful_resolution(self) -> bool:
191
220
  return self.resolution in [Resolution.CANCEL, Resolution.MKT]
192
221
 
222
+ def get_outcome_str_from_bool(self, outcome: bool) -> OutcomeStr:
223
+ raise NotImplementedError("Subclasses must implement this method")
224
+
193
225
  def get_outcome_str(self, outcome_index: int) -> str:
194
226
  try:
195
227
  return self.outcomes[outcome_index]
@@ -207,6 +239,9 @@ class AgentMarket(BaseModel):
207
239
  def get_token_balance(self, user_id: str, outcome: str) -> TokenAmount:
208
240
  raise NotImplementedError("Subclasses must implement this method")
209
241
 
242
+ def get_position(self, user_id: str) -> Position | None:
243
+ raise NotImplementedError("Subclasses must implement this method")
244
+
210
245
  @classmethod
211
246
  def get_positions(
212
247
  cls, user_id: str, liquid_only: bool = False, larger_than: float = 0
@@ -236,3 +271,12 @@ class AgentMarket(BaseModel):
236
271
  @classmethod
237
272
  def get_user_url(cls, keys: APIKeys) -> str:
238
273
  raise NotImplementedError("Subclasses must implement this method")
274
+
275
+ def has_token_pool(self) -> bool:
276
+ return self.outcome_token_pool is not None
277
+
278
+ def get_pool_tokens(self, outcome: str) -> float:
279
+ if not self.outcome_token_pool:
280
+ raise ValueError("Outcome token pool is not available.")
281
+
282
+ return self.outcome_token_pool[outcome]
@@ -115,3 +115,14 @@ class Position(BaseModel):
115
115
  for outcome, amount in self.amounts.items()
116
116
  )
117
117
  return f"Position for market id {self.market_id}: {amounts_str}"
118
+
119
+
120
+ class TradeType(str, Enum):
121
+ SELL = "sell"
122
+ BUY = "buy"
123
+
124
+
125
+ class Trade(BaseModel):
126
+ trade_type: TradeType
127
+ outcome: bool
128
+ amount: TokenAmount
@@ -19,6 +19,12 @@ class ManifoldPool(BaseModel):
19
19
  NO: float
20
20
  YES: float
21
21
 
22
+ def size_for_outcome(self, outcome: str) -> float:
23
+ if hasattr(self, outcome):
24
+ return float(getattr(self, outcome))
25
+ else:
26
+ should_not_happen(f"Unexpected outcome string, '{outcome}'.")
27
+
22
28
 
23
29
  class ManifoldAnswersMode(str, Enum):
24
30
  ANYONE = "ANYONE"
@@ -72,6 +72,9 @@ class ManifoldAgentMarket(AgentMarket):
72
72
  current_p_yes=model.probability,
73
73
  url=model.url,
74
74
  volume=model.volume,
75
+ outcome_token_pool={
76
+ o: model.pool.size_for_outcome(o) for o in model.outcomes
77
+ },
75
78
  )
76
79
 
77
80
  @staticmethod
@@ -43,6 +43,7 @@ class MetaculusAgentMarket(AgentMarket):
43
43
  volume=None,
44
44
  have_predicted=model.my_predictions is not None
45
45
  and len(model.my_predictions.predictions) > 0,
46
+ outcome_token_pool=None,
46
47
  )
47
48
 
48
49
  @staticmethod
@@ -44,6 +44,10 @@ def get_boolean_outcome(outcome_str: str) -> bool:
44
44
  raise ValueError(f"Outcome `{outcome_str}` is not a valid boolean outcome.")
45
45
 
46
46
 
47
+ def get_bet_outcome(binary_outcome: bool) -> str:
48
+ return OMEN_TRUE_OUTCOME if binary_outcome else OMEN_FALSE_OUTCOME
49
+
50
+
47
51
  class Condition(BaseModel):
48
52
  id: HexBytes
49
53
  outcomeSlotCount: int
@@ -40,6 +40,8 @@ from prediction_market_agent_tooling.markets.omen.data_models import (
40
40
  OmenBet,
41
41
  OmenMarket,
42
42
  OmenUserPosition,
43
+ get_bet_outcome,
44
+ get_boolean_outcome,
43
45
  )
44
46
  from prediction_market_agent_tooling.markets.omen.omen_contracts import (
45
47
  OMEN_DEFAULT_MARKET_FEE,
@@ -75,6 +77,7 @@ from prediction_market_agent_tooling.tools.web3_utils import (
75
77
  )
76
78
 
77
79
  OMEN_DEFAULT_REALITIO_BOND_VALUE = xdai_type(0.01)
80
+ OMEN_TINY_BET_AMOUNT = xdai_type(0.00001)
78
81
 
79
82
 
80
83
  class OmenAgentMarket(AgentMarket):
@@ -147,7 +150,41 @@ class OmenAgentMarket(AgentMarket):
147
150
 
148
151
  @classmethod
149
152
  def get_tiny_bet_amount(cls) -> BetAmount:
150
- return BetAmount(amount=0.00001, currency=cls.currency)
153
+ return BetAmount(amount=OMEN_TINY_BET_AMOUNT, currency=cls.currency)
154
+
155
+ def liquidate_existing_positions(
156
+ self,
157
+ bet_outcome: bool,
158
+ web3: Web3 | None = None,
159
+ api_keys: APIKeys | None = None,
160
+ larger_than: float | None = None,
161
+ ) -> None:
162
+ """
163
+ Liquidates all previously existing positions.
164
+ Returns the amount in collateral obtained by selling the positions.
165
+ """
166
+ api_keys = api_keys if api_keys is not None else APIKeys()
167
+ better_address = api_keys.bet_from_address
168
+ larger_than = (
169
+ larger_than
170
+ if larger_than is not None
171
+ else self.get_liquidatable_amount().amount
172
+ )
173
+ prev_positions_for_market = self.get_positions(
174
+ user_id=better_address, liquid_only=True, larger_than=larger_than
175
+ )
176
+
177
+ for prev_position in prev_positions_for_market:
178
+ for position_outcome, token_amount in prev_position.amounts.items():
179
+ position_outcome_bool = get_boolean_outcome(position_outcome)
180
+ if position_outcome_bool != bet_outcome:
181
+ # We keep it as collateral since we want to place a bet immediately after this function.
182
+ self.sell_tokens(
183
+ outcome=position_outcome_bool,
184
+ amount=token_amount,
185
+ auto_withdraw=False,
186
+ web3=web3,
187
+ )
151
188
 
152
189
  def place_bet(
153
190
  self,
@@ -310,6 +347,10 @@ class OmenAgentMarket(AgentMarket):
310
347
  volume=wei_to_xdai(model.collateralVolume),
311
348
  close_time=model.close_time,
312
349
  fee=float(wei_to_xdai(model.fee)) if model.fee is not None else 0.0,
350
+ outcome_token_pool={
351
+ model.outcomes[i]: wei_to_xdai(Wei(model.outcomeTokenAmounts[i]))
352
+ for i in range(len(model.outcomes))
353
+ },
313
354
  )
314
355
 
315
356
  @staticmethod
@@ -381,6 +422,11 @@ class OmenAgentMarket(AgentMarket):
381
422
  cls.get_outcome_str(cls.index_set_to_outcome_index(index_set))
382
423
  )
383
424
 
425
+ def get_outcome_str_from_bool(self, outcome: bool) -> OutcomeStr:
426
+ return (
427
+ OutcomeStr(OMEN_TRUE_OUTCOME) if outcome else OutcomeStr(OMEN_FALSE_OUTCOME)
428
+ )
429
+
384
430
  def get_token_balance(
385
431
  self, user_id: str, outcome: str, web3: Web3 | None = None
386
432
  ) -> TokenAmount:
@@ -393,6 +439,18 @@ class OmenAgentMarket(AgentMarket):
393
439
  currency=self.currency,
394
440
  )
395
441
 
442
+ def get_position(self, user_id: str) -> Position | None:
443
+ liquidatable_amount = self.get_liquidatable_amount()
444
+ existing_positions = self.get_positions(
445
+ user_id=user_id,
446
+ liquid_only=True,
447
+ larger_than=liquidatable_amount.amount,
448
+ )
449
+ existing_position = next(
450
+ iter([i for i in existing_positions if i.market_id == self.id]), None
451
+ )
452
+ return existing_position
453
+
396
454
  @classmethod
397
455
  def get_positions(
398
456
  cls,
@@ -553,12 +611,15 @@ def omen_buy_outcome_tx(
553
611
  market_contract: OmenFixedProductMarketMakerContract = market.get_contract()
554
612
  collateral_token_contract = market_contract.get_collateral_token_contract()
555
613
 
614
+ # In case of ERC4626, obtained (for example) sDai out of xDai could be lower than the `amount_wei`, so we need to handle it.
615
+ amount_wei_to_buy = collateral_token_contract.get_in_shares(amount_wei, web3)
616
+
556
617
  # Get the index of the outcome we want to buy.
557
618
  outcome_index: int = market.get_outcome_index(outcome)
558
619
 
559
620
  # Calculate the amount of shares we will get for the given investment amount.
560
621
  expected_shares = market_contract.calcBuyAmount(
561
- amount_wei, outcome_index, web3=web3
622
+ amount_wei_to_buy, outcome_index, web3=web3
562
623
  )
563
624
  # Allow 1% slippage.
564
625
  expected_shares = remove_fraction(expected_shares, 0.01)
@@ -566,11 +627,12 @@ def omen_buy_outcome_tx(
566
627
  collateral_token_contract.approve(
567
628
  api_keys=api_keys,
568
629
  for_address=market_contract.address,
569
- amount_wei=amount_wei,
630
+ amount_wei=amount_wei_to_buy,
570
631
  web3=web3,
571
632
  )
572
633
 
573
634
  if auto_deposit:
635
+ # In auto-depositing, we need to deposit the original `amount_wei`, e.g. we can deposit 2 xDai, but receive 1.8 sDai, so for the bet we will use `amount_wei_to_buy`.
574
636
  auto_deposit_collateral_token(
575
637
  collateral_token_contract, amount_wei, api_keys, web3
576
638
  )
@@ -578,7 +640,7 @@ def omen_buy_outcome_tx(
578
640
  # Buy shares using the deposited xDai in the collateral token.
579
641
  market_contract.buy(
580
642
  api_keys=api_keys,
581
- amount_wei=amount_wei,
643
+ amount_wei=amount_wei_to_buy,
582
644
  outcome_index=outcome_index,
583
645
  min_outcome_tokens_to_buy=expected_shares,
584
646
  web3=web3,
@@ -597,7 +659,7 @@ def binary_omen_buy_outcome_tx(
597
659
  api_keys=api_keys,
598
660
  amount=amount,
599
661
  market=market,
600
- outcome=OMEN_TRUE_OUTCOME if binary_outcome else OMEN_FALSE_OUTCOME,
662
+ outcome=get_bet_outcome(binary_outcome),
601
663
  auto_deposit=auto_deposit,
602
664
  web3=web3,
603
665
  )
@@ -686,7 +748,7 @@ def binary_omen_sell_outcome_tx(
686
748
  api_keys=api_keys,
687
749
  amount=amount,
688
750
  market=market,
689
- outcome=OMEN_TRUE_OUTCOME if binary_outcome else OMEN_FALSE_OUTCOME,
751
+ outcome=get_bet_outcome(binary_outcome),
690
752
  auto_withdraw=auto_withdraw,
691
753
  web3=web3,
692
754
  )
@@ -769,6 +831,7 @@ def omen_create_market_tx(
769
831
  web3=web3,
770
832
  )
771
833
 
834
+ # In case of ERC4626, obtained (for example) sDai out of xDai could be lower than the `amount_wei`, so we need to handle it.
772
835
  initial_funds_in_shares = collateral_token_contract.get_in_shares(
773
836
  amount=initial_funds_wei, web3=web3
774
837
  )