prediction-market-agent-tooling 0.48.5__tar.gz → 0.48.8__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 (84) hide show
  1. {prediction_market_agent_tooling-0.48.5 → prediction_market_agent_tooling-0.48.8}/PKG-INFO +1 -1
  2. {prediction_market_agent_tooling-0.48.5 → prediction_market_agent_tooling-0.48.8}/prediction_market_agent_tooling/benchmark/agents.py +0 -2
  3. {prediction_market_agent_tooling-0.48.5 → prediction_market_agent_tooling-0.48.8}/prediction_market_agent_tooling/benchmark/utils.py +5 -3
  4. {prediction_market_agent_tooling-0.48.5 → prediction_market_agent_tooling-0.48.8}/prediction_market_agent_tooling/deploy/agent.py +35 -27
  5. {prediction_market_agent_tooling-0.48.5 → prediction_market_agent_tooling-0.48.8}/prediction_market_agent_tooling/deploy/agent_example.py +5 -6
  6. prediction_market_agent_tooling-0.48.8/prediction_market_agent_tooling/deploy/betting_strategy.py +62 -0
  7. {prediction_market_agent_tooling-0.48.5 → prediction_market_agent_tooling-0.48.8}/prediction_market_agent_tooling/markets/data_models.py +44 -3
  8. {prediction_market_agent_tooling-0.48.5 → prediction_market_agent_tooling-0.48.8}/prediction_market_agent_tooling/markets/omen/data_models.py +71 -3
  9. {prediction_market_agent_tooling-0.48.5 → prediction_market_agent_tooling-0.48.8}/prediction_market_agent_tooling/markets/omen/omen_contracts.py +3 -2
  10. {prediction_market_agent_tooling-0.48.5 → prediction_market_agent_tooling-0.48.8}/prediction_market_agent_tooling/markets/omen/omen_resolving.py +22 -44
  11. {prediction_market_agent_tooling-0.48.5 → prediction_market_agent_tooling-0.48.8}/prediction_market_agent_tooling/markets/omen/omen_subgraph_handler.py +63 -1
  12. {prediction_market_agent_tooling-0.48.5 → prediction_market_agent_tooling-0.48.8}/prediction_market_agent_tooling/markets/polymarket/data_models_web.py +41 -38
  13. {prediction_market_agent_tooling-0.48.5 → prediction_market_agent_tooling-0.48.8}/prediction_market_agent_tooling/monitor/monitor_app.py +9 -8
  14. {prediction_market_agent_tooling-0.48.5 → prediction_market_agent_tooling-0.48.8}/prediction_market_agent_tooling/tools/betting_strategies/kelly_criterion.py +6 -13
  15. {prediction_market_agent_tooling-0.48.5 → prediction_market_agent_tooling-0.48.8}/prediction_market_agent_tooling/tools/is_predictable.py +1 -1
  16. {prediction_market_agent_tooling-0.48.5 → prediction_market_agent_tooling-0.48.8}/pyproject.toml +1 -1
  17. {prediction_market_agent_tooling-0.48.5 → prediction_market_agent_tooling-0.48.8}/LICENSE +0 -0
  18. {prediction_market_agent_tooling-0.48.5 → prediction_market_agent_tooling-0.48.8}/README.md +0 -0
  19. {prediction_market_agent_tooling-0.48.5 → prediction_market_agent_tooling-0.48.8}/prediction_market_agent_tooling/abis/depositablewrapper_erc20.abi.json +0 -0
  20. {prediction_market_agent_tooling-0.48.5 → prediction_market_agent_tooling-0.48.8}/prediction_market_agent_tooling/abis/erc20.abi.json +0 -0
  21. {prediction_market_agent_tooling-0.48.5 → prediction_market_agent_tooling-0.48.8}/prediction_market_agent_tooling/abis/erc4626.abi.json +0 -0
  22. {prediction_market_agent_tooling-0.48.5 → prediction_market_agent_tooling-0.48.8}/prediction_market_agent_tooling/abis/omen_dxdao.abi.json +0 -0
  23. {prediction_market_agent_tooling-0.48.5 → prediction_market_agent_tooling-0.48.8}/prediction_market_agent_tooling/abis/omen_fpmm.abi.json +0 -0
  24. {prediction_market_agent_tooling-0.48.5 → prediction_market_agent_tooling-0.48.8}/prediction_market_agent_tooling/abis/omen_fpmm_conditionaltokens.abi.json +0 -0
  25. {prediction_market_agent_tooling-0.48.5 → prediction_market_agent_tooling-0.48.8}/prediction_market_agent_tooling/abis/omen_fpmm_factory.abi.json +0 -0
  26. {prediction_market_agent_tooling-0.48.5 → prediction_market_agent_tooling-0.48.8}/prediction_market_agent_tooling/abis/omen_kleros.abi.json +0 -0
  27. {prediction_market_agent_tooling-0.48.5 → prediction_market_agent_tooling-0.48.8}/prediction_market_agent_tooling/abis/omen_oracle.abi.json +0 -0
  28. {prediction_market_agent_tooling-0.48.5 → prediction_market_agent_tooling-0.48.8}/prediction_market_agent_tooling/abis/omen_realitio.abi.json +0 -0
  29. {prediction_market_agent_tooling-0.48.5 → prediction_market_agent_tooling-0.48.8}/prediction_market_agent_tooling/abis/omen_thumbnailmapping.abi.json +0 -0
  30. {prediction_market_agent_tooling-0.48.5 → prediction_market_agent_tooling-0.48.8}/prediction_market_agent_tooling/abis/proxy.abi.json +0 -0
  31. {prediction_market_agent_tooling-0.48.5 → prediction_market_agent_tooling-0.48.8}/prediction_market_agent_tooling/benchmark/__init__.py +0 -0
  32. {prediction_market_agent_tooling-0.48.5 → prediction_market_agent_tooling-0.48.8}/prediction_market_agent_tooling/benchmark/benchmark.py +0 -0
  33. {prediction_market_agent_tooling-0.48.5 → prediction_market_agent_tooling-0.48.8}/prediction_market_agent_tooling/config.py +0 -0
  34. {prediction_market_agent_tooling-0.48.5 → prediction_market_agent_tooling-0.48.8}/prediction_market_agent_tooling/deploy/constants.py +0 -0
  35. {prediction_market_agent_tooling-0.48.5 → prediction_market_agent_tooling-0.48.8}/prediction_market_agent_tooling/deploy/gcp/deploy.py +0 -0
  36. {prediction_market_agent_tooling-0.48.5 → prediction_market_agent_tooling-0.48.8}/prediction_market_agent_tooling/deploy/gcp/kubernetes_models.py +0 -0
  37. {prediction_market_agent_tooling-0.48.5 → prediction_market_agent_tooling-0.48.8}/prediction_market_agent_tooling/deploy/gcp/utils.py +0 -0
  38. {prediction_market_agent_tooling-0.48.5 → prediction_market_agent_tooling-0.48.8}/prediction_market_agent_tooling/gtypes.py +0 -0
  39. {prediction_market_agent_tooling-0.48.5 → prediction_market_agent_tooling-0.48.8}/prediction_market_agent_tooling/loggers.py +0 -0
  40. {prediction_market_agent_tooling-0.48.5 → prediction_market_agent_tooling-0.48.8}/prediction_market_agent_tooling/markets/agent_market.py +0 -0
  41. {prediction_market_agent_tooling-0.48.5 → prediction_market_agent_tooling-0.48.8}/prediction_market_agent_tooling/markets/categorize.py +0 -0
  42. {prediction_market_agent_tooling-0.48.5 → prediction_market_agent_tooling-0.48.8}/prediction_market_agent_tooling/markets/manifold/__init__.py +0 -0
  43. {prediction_market_agent_tooling-0.48.5 → prediction_market_agent_tooling-0.48.8}/prediction_market_agent_tooling/markets/manifold/api.py +0 -0
  44. {prediction_market_agent_tooling-0.48.5 → prediction_market_agent_tooling-0.48.8}/prediction_market_agent_tooling/markets/manifold/data_models.py +0 -0
  45. {prediction_market_agent_tooling-0.48.5 → prediction_market_agent_tooling-0.48.8}/prediction_market_agent_tooling/markets/manifold/manifold.py +0 -0
  46. {prediction_market_agent_tooling-0.48.5 → prediction_market_agent_tooling-0.48.8}/prediction_market_agent_tooling/markets/manifold/utils.py +0 -0
  47. {prediction_market_agent_tooling-0.48.5 → prediction_market_agent_tooling-0.48.8}/prediction_market_agent_tooling/markets/markets.py +0 -0
  48. {prediction_market_agent_tooling-0.48.5 → prediction_market_agent_tooling-0.48.8}/prediction_market_agent_tooling/markets/metaculus/api.py +0 -0
  49. {prediction_market_agent_tooling-0.48.5 → prediction_market_agent_tooling-0.48.8}/prediction_market_agent_tooling/markets/metaculus/data_models.py +0 -0
  50. {prediction_market_agent_tooling-0.48.5 → prediction_market_agent_tooling-0.48.8}/prediction_market_agent_tooling/markets/metaculus/metaculus.py +0 -0
  51. {prediction_market_agent_tooling-0.48.5 → prediction_market_agent_tooling-0.48.8}/prediction_market_agent_tooling/markets/omen/__init__.py +0 -0
  52. {prediction_market_agent_tooling-0.48.5 → prediction_market_agent_tooling-0.48.8}/prediction_market_agent_tooling/markets/omen/omen.py +0 -0
  53. {prediction_market_agent_tooling-0.48.5 → prediction_market_agent_tooling-0.48.8}/prediction_market_agent_tooling/markets/polymarket/api.py +0 -0
  54. {prediction_market_agent_tooling-0.48.5 → prediction_market_agent_tooling-0.48.8}/prediction_market_agent_tooling/markets/polymarket/data_models.py +0 -0
  55. {prediction_market_agent_tooling-0.48.5 → prediction_market_agent_tooling-0.48.8}/prediction_market_agent_tooling/markets/polymarket/polymarket.py +0 -0
  56. {prediction_market_agent_tooling-0.48.5 → prediction_market_agent_tooling-0.48.8}/prediction_market_agent_tooling/markets/polymarket/utils.py +0 -0
  57. {prediction_market_agent_tooling-0.48.5 → prediction_market_agent_tooling-0.48.8}/prediction_market_agent_tooling/monitor/markets/manifold.py +0 -0
  58. {prediction_market_agent_tooling-0.48.5 → prediction_market_agent_tooling-0.48.8}/prediction_market_agent_tooling/monitor/markets/metaculus.py +0 -0
  59. {prediction_market_agent_tooling-0.48.5 → prediction_market_agent_tooling-0.48.8}/prediction_market_agent_tooling/monitor/markets/omen.py +0 -0
  60. {prediction_market_agent_tooling-0.48.5 → prediction_market_agent_tooling-0.48.8}/prediction_market_agent_tooling/monitor/markets/polymarket.py +0 -0
  61. {prediction_market_agent_tooling-0.48.5 → prediction_market_agent_tooling-0.48.8}/prediction_market_agent_tooling/monitor/monitor.py +0 -0
  62. {prediction_market_agent_tooling-0.48.5 → prediction_market_agent_tooling-0.48.8}/prediction_market_agent_tooling/monitor/monitor_settings.py +0 -0
  63. {prediction_market_agent_tooling-0.48.5 → prediction_market_agent_tooling-0.48.8}/prediction_market_agent_tooling/py.typed +0 -0
  64. {prediction_market_agent_tooling-0.48.5 → prediction_market_agent_tooling-0.48.8}/prediction_market_agent_tooling/tools/balances.py +0 -0
  65. {prediction_market_agent_tooling-0.48.5 → prediction_market_agent_tooling-0.48.8}/prediction_market_agent_tooling/tools/betting_strategies/market_moving.py +0 -0
  66. {prediction_market_agent_tooling-0.48.5 → prediction_market_agent_tooling-0.48.8}/prediction_market_agent_tooling/tools/betting_strategies/minimum_bet_to_win.py +0 -0
  67. {prediction_market_agent_tooling-0.48.5 → prediction_market_agent_tooling-0.48.8}/prediction_market_agent_tooling/tools/betting_strategies/stretch_bet_between.py +0 -0
  68. {prediction_market_agent_tooling-0.48.5 → prediction_market_agent_tooling-0.48.8}/prediction_market_agent_tooling/tools/cache.py +0 -0
  69. {prediction_market_agent_tooling-0.48.5 → prediction_market_agent_tooling-0.48.8}/prediction_market_agent_tooling/tools/contract.py +0 -0
  70. {prediction_market_agent_tooling-0.48.5 → prediction_market_agent_tooling-0.48.8}/prediction_market_agent_tooling/tools/costs.py +0 -0
  71. {prediction_market_agent_tooling-0.48.5 → prediction_market_agent_tooling-0.48.8}/prediction_market_agent_tooling/tools/gnosis_rpc.py +0 -0
  72. {prediction_market_agent_tooling-0.48.5 → prediction_market_agent_tooling-0.48.8}/prediction_market_agent_tooling/tools/google.py +0 -0
  73. {prediction_market_agent_tooling-0.48.5 → prediction_market_agent_tooling-0.48.8}/prediction_market_agent_tooling/tools/hexbytes_custom.py +0 -0
  74. {prediction_market_agent_tooling-0.48.5 → prediction_market_agent_tooling-0.48.8}/prediction_market_agent_tooling/tools/image_gen/image_gen.py +0 -0
  75. {prediction_market_agent_tooling-0.48.5 → prediction_market_agent_tooling-0.48.8}/prediction_market_agent_tooling/tools/image_gen/market_thumbnail_gen.py +0 -0
  76. {prediction_market_agent_tooling-0.48.5 → prediction_market_agent_tooling-0.48.8}/prediction_market_agent_tooling/tools/langfuse_.py +0 -0
  77. {prediction_market_agent_tooling-0.48.5 → prediction_market_agent_tooling-0.48.8}/prediction_market_agent_tooling/tools/parallelism.py +0 -0
  78. {prediction_market_agent_tooling-0.48.5 → prediction_market_agent_tooling-0.48.8}/prediction_market_agent_tooling/tools/safe.py +0 -0
  79. {prediction_market_agent_tooling-0.48.5 → prediction_market_agent_tooling-0.48.8}/prediction_market_agent_tooling/tools/singleton.py +0 -0
  80. {prediction_market_agent_tooling-0.48.5 → prediction_market_agent_tooling-0.48.8}/prediction_market_agent_tooling/tools/streamlit_user_login.py +0 -0
  81. {prediction_market_agent_tooling-0.48.5 → prediction_market_agent_tooling-0.48.8}/prediction_market_agent_tooling/tools/tavily_storage/tavily_models.py +0 -0
  82. {prediction_market_agent_tooling-0.48.5 → prediction_market_agent_tooling-0.48.8}/prediction_market_agent_tooling/tools/tavily_storage/tavily_storage.py +0 -0
  83. {prediction_market_agent_tooling-0.48.5 → prediction_market_agent_tooling-0.48.8}/prediction_market_agent_tooling/tools/utils.py +0 -0
  84. {prediction_market_agent_tooling-0.48.5 → prediction_market_agent_tooling-0.48.8}/prediction_market_agent_tooling/tools/web3_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.5
3
+ Version: 0.48.8
4
4
  Summary: Tools to benchmark, deploy and monitor prediction market agents.
5
5
  Author: Gnosis
6
6
  Requires-Python: >=3.10,<3.12
@@ -87,7 +87,6 @@ class RandomAgent(AbstractBenchmarkedAgent):
87
87
  p_yes, confidence = random.random(), random.random()
88
88
  return Prediction(
89
89
  outcome_prediction=OutcomePrediction(
90
- decision=p_yes > 0.5,
91
90
  p_yes=Probability(p_yes),
92
91
  confidence=confidence,
93
92
  info_utility=None,
@@ -111,7 +110,6 @@ class FixedAgent(AbstractBenchmarkedAgent):
111
110
  p_yes, confidence = 1.0 if self.fixed_answer else 0.0, 1.0
112
111
  return Prediction(
113
112
  outcome_prediction=OutcomePrediction(
114
- decision=self.fixed_answer,
115
113
  p_yes=Probability(p_yes),
116
114
  confidence=confidence,
117
115
  info_utility=None,
@@ -3,11 +3,13 @@ import typing as t
3
3
 
4
4
  from pydantic import BaseModel
5
5
 
6
- from prediction_market_agent_tooling.deploy.agent import Answer
7
- from prediction_market_agent_tooling.markets.data_models import Resolution
6
+ from prediction_market_agent_tooling.markets.data_models import (
7
+ ProbabilisticAnswer,
8
+ Resolution,
9
+ )
8
10
 
9
11
 
10
- class OutcomePrediction(Answer):
12
+ class OutcomePrediction(ProbabilisticAnswer):
11
13
  info_utility: t.Optional[float]
12
14
 
13
15
  @property
@@ -12,6 +12,10 @@ from pydantic import BaseModel, BeforeValidator, computed_field
12
12
  from typing_extensions import Annotated
13
13
 
14
14
  from prediction_market_agent_tooling.config import APIKeys
15
+ from prediction_market_agent_tooling.deploy.betting_strategy import (
16
+ BettingStrategy,
17
+ MaxAccuracyBettingStrategy,
18
+ )
15
19
  from prediction_market_agent_tooling.deploy.constants import (
16
20
  MARKET_TYPE_KEY,
17
21
  REPOSITORY_KEY,
@@ -25,14 +29,19 @@ from prediction_market_agent_tooling.deploy.gcp.utils import (
25
29
  gcp_function_is_active,
26
30
  gcp_resolve_api_keys_secrets,
27
31
  )
28
- from prediction_market_agent_tooling.gtypes import Probability, xDai, xdai_type
32
+ from prediction_market_agent_tooling.gtypes import xDai, xdai_type
29
33
  from prediction_market_agent_tooling.loggers import logger
30
34
  from prediction_market_agent_tooling.markets.agent_market import (
31
35
  AgentMarket,
32
36
  FilterBy,
33
37
  SortBy,
34
38
  )
35
- from prediction_market_agent_tooling.markets.data_models import BetAmount
39
+ from prediction_market_agent_tooling.markets.data_models import (
40
+ BetAmount,
41
+ ProbabilisticAnswer,
42
+ TokenAmount,
43
+ TokenAmountAndDirection,
44
+ )
36
45
  from prediction_market_agent_tooling.markets.markets import (
37
46
  MarketType,
38
47
  have_bet_on_market_since,
@@ -95,19 +104,8 @@ class OutOfFundsError(ValueError):
95
104
  pass
96
105
 
97
106
 
98
- class Answer(BaseModel):
99
- decision: Decision # Warning: p_yes > 0.5 doesn't necessarily mean decision is True! For example, if our p_yes is 55%, but market's p_yes is 80%, then it might be profitable to bet on False.
100
- p_yes: Probability
101
- confidence: float
102
- reasoning: str | None = None
103
-
104
- @property
105
- def p_no(self) -> Probability:
106
- return Probability(1 - self.p_yes)
107
-
108
-
109
107
  class ProcessedMarket(BaseModel):
110
- answer: Answer
108
+ answer: ProbabilisticAnswer
111
109
  amount: BetAmount
112
110
 
113
111
 
@@ -280,6 +278,7 @@ class DeployableTraderAgent(DeployableAgent):
280
278
  bet_on_n_markets_per_run: int = 1
281
279
  min_required_balance_to_operate: xDai | None = xdai_type(1)
282
280
  min_balance_to_keep_in_native_currency: xDai | None = xdai_type(0.1)
281
+ strategy: BettingStrategy = MaxAccuracyBettingStrategy()
283
282
 
284
283
  def __init__(
285
284
  self,
@@ -295,7 +294,7 @@ class DeployableTraderAgent(DeployableAgent):
295
294
  self.have_bet_on_market_since = observe()(self.have_bet_on_market_since) # type: ignore[method-assign]
296
295
  self.verify_market = observe()(self.verify_market) # type: ignore[method-assign]
297
296
  self.answer_binary_market = observe()(self.answer_binary_market) # type: ignore[method-assign]
298
- self.calculate_bet_amount = observe()(self.calculate_bet_amount) # type: ignore[method-assign]
297
+ self.calculate_bet_amount_and_direction = observe()(self.calculate_bet_amount_and_direction) # type: ignore[method-assign]
299
298
  self.process_market = observe()(self.process_market) # type: ignore[method-assign]
300
299
 
301
300
  def update_langfuse_trace_by_market(
@@ -311,6 +310,18 @@ class DeployableTraderAgent(DeployableAgent):
311
310
  },
312
311
  )
313
312
 
313
+ def calculate_bet_amount_and_direction(
314
+ self, answer: ProbabilisticAnswer, market: AgentMarket
315
+ ) -> TokenAmountAndDirection:
316
+ amount_and_direction = self.strategy.calculate_bet_amount_and_direction(
317
+ answer, market
318
+ )
319
+ if amount_and_direction.currency != market.currency:
320
+ raise ValueError(
321
+ f"Currency mismatch. Strategy yields {amount_and_direction.currency}, market has currency {market.currency}"
322
+ )
323
+ return amount_and_direction
324
+
314
325
  def update_langfuse_trace_by_processed_market(
315
326
  self, market_type: MarketType, processed_market: ProcessedMarket | None
316
327
  ) -> None:
@@ -360,18 +371,12 @@ class DeployableTraderAgent(DeployableAgent):
360
371
 
361
372
  return True
362
373
 
363
- def answer_binary_market(self, market: AgentMarket) -> Answer | None:
374
+ def answer_binary_market(self, market: AgentMarket) -> ProbabilisticAnswer | None:
364
375
  """
365
376
  Answer the binary market. This method must be implemented by the subclass.
366
377
  """
367
378
  raise NotImplementedError("This method must be implemented by the subclass")
368
379
 
369
- def calculate_bet_amount(self, answer: Answer, market: AgentMarket) -> BetAmount:
370
- """
371
- Calculate the bet amount. By default, it returns the minimum bet amount.
372
- """
373
- return market.get_tiny_bet_amount()
374
-
375
380
  def get_markets(
376
381
  self,
377
382
  market_type: MarketType,
@@ -408,22 +413,25 @@ class DeployableTraderAgent(DeployableAgent):
408
413
  self.update_langfuse_trace_by_processed_market(market_type, None)
409
414
  return None
410
415
 
411
- amount = self.calculate_bet_amount(answer, market)
416
+ amount_and_direction = self.calculate_bet_amount_and_direction(answer, market)
412
417
 
413
418
  if self.place_bet:
414
419
  logger.info(
415
- f"Placing bet on {market} with result {answer} and amount {amount}"
420
+ f"Placing bet on {market} with direction {amount_and_direction.direction} and amount {amount_and_direction.amount}"
416
421
  )
417
422
  market.place_bet(
418
- amount=amount,
419
- outcome=answer.decision,
423
+ amount=TokenAmount(
424
+ amount=amount_and_direction.amount,
425
+ currency=amount_and_direction.currency,
426
+ ),
427
+ outcome=amount_and_direction.direction,
420
428
  )
421
429
 
422
430
  self.after_process_market(market_type, market)
423
431
 
424
432
  processed_market = ProcessedMarket(
425
433
  answer=answer,
426
- amount=amount,
434
+ amount=amount_and_direction,
427
435
  )
428
436
  self.update_langfuse_trace_by_processed_market(market_type, processed_market)
429
437
 
@@ -1,10 +1,10 @@
1
1
  import random
2
2
 
3
3
  from prediction_market_agent_tooling.deploy.agent import (
4
- Answer,
5
4
  DeployableTraderAgent,
6
- Probability,
5
+ ProbabilisticAnswer,
7
6
  )
7
+ from prediction_market_agent_tooling.gtypes import Probability
8
8
  from prediction_market_agent_tooling.markets.agent_market import AgentMarket
9
9
  from prediction_market_agent_tooling.markets.markets import MarketType
10
10
 
@@ -13,10 +13,9 @@ class DeployableCoinFlipAgent(DeployableTraderAgent):
13
13
  def verify_market(self, market_type: MarketType, market: AgentMarket) -> bool:
14
14
  return True
15
15
 
16
- def answer_binary_market(self, market: AgentMarket) -> Answer | None:
16
+ def answer_binary_market(self, market: AgentMarket) -> ProbabilisticAnswer | None:
17
17
  decision = random.choice([True, False])
18
- return Answer(
19
- decision=decision,
18
+ return ProbabilisticAnswer(
20
19
  p_yes=Probability(float(decision)),
21
20
  confidence=0.5,
22
21
  reasoning="I flipped a coin to decide.",
@@ -24,5 +23,5 @@ class DeployableCoinFlipAgent(DeployableTraderAgent):
24
23
 
25
24
 
26
25
  class DeployableAlwaysRaiseAgent(DeployableTraderAgent):
27
- def answer_binary_market(self, market: AgentMarket) -> Answer | None:
26
+ def answer_binary_market(self, market: AgentMarket) -> ProbabilisticAnswer | None:
28
27
  raise RuntimeError("I always raise!")
@@ -0,0 +1,62 @@
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
+ ProbabilisticAnswer,
6
+ TokenAmountAndDirection,
7
+ )
8
+ from prediction_market_agent_tooling.tools.betting_strategies.kelly_criterion import (
9
+ get_kelly_bet,
10
+ )
11
+
12
+
13
+ class BettingStrategy(ABC):
14
+ @abstractmethod
15
+ def calculate_bet_amount_and_direction(
16
+ self, answer: ProbabilisticAnswer, market: AgentMarket
17
+ ) -> TokenAmountAndDirection:
18
+ pass
19
+
20
+
21
+ class MaxAccuracyBettingStrategy(BettingStrategy):
22
+ def __init__(self, bet_amount: float | None = None):
23
+ self.bet_amount = bet_amount
24
+
25
+ @staticmethod
26
+ def calculate_direction(market_p_yes: float, estimate_p_yes: float) -> bool:
27
+ # If estimate_p_yes >= market.current_p_yes, then bet TRUE, else bet FALSE.
28
+ # This is equivalent to saying EXPECTED_VALUE = (estimate_p_yes * num_tokens_obtained_by_betting_yes) -
29
+ # ((1 - estimate_p_yes) * num_tokens_obtained_by_betting_no) >= 0
30
+ return estimate_p_yes >= market_p_yes
31
+
32
+ def calculate_bet_amount_and_direction(
33
+ self, answer: ProbabilisticAnswer, market: AgentMarket
34
+ ) -> TokenAmountAndDirection:
35
+ bet_amount = (
36
+ market.get_tiny_bet_amount().amount
37
+ if self.bet_amount is None
38
+ else self.bet_amount
39
+ )
40
+ direction = self.calculate_direction(market.current_p_yes, answer.p_yes)
41
+ return TokenAmountAndDirection(
42
+ amount=bet_amount,
43
+ currency=market.currency,
44
+ direction=direction,
45
+ )
46
+
47
+
48
+ class KellyBettingStrategy(BettingStrategy):
49
+ def __init__(self, max_bet_amount: float = 10):
50
+ self.max_bet_amount = max_bet_amount
51
+
52
+ def calculate_bet_amount_and_direction(
53
+ self, answer: ProbabilisticAnswer, market: AgentMarket
54
+ ) -> TokenAmountAndDirection:
55
+ kelly_bet = get_kelly_bet(
56
+ self.max_bet_amount, market.current_p_yes, answer.p_yes, answer.confidence
57
+ )
58
+ return TokenAmountAndDirection(
59
+ amount=kelly_bet.size,
60
+ currency=market.currency,
61
+ direction=kelly_bet.direction,
62
+ )
@@ -1,10 +1,10 @@
1
1
  from datetime import datetime
2
2
  from enum import Enum
3
- from typing import TypeAlias
3
+ from typing import Annotated, TypeAlias
4
4
 
5
- from pydantic import BaseModel, computed_field
5
+ from pydantic import BaseModel, BeforeValidator, computed_field
6
6
 
7
- from prediction_market_agent_tooling.gtypes import OutcomeStr
7
+ from prediction_market_agent_tooling.gtypes import OutcomeStr, Probability
8
8
 
9
9
 
10
10
  class Currency(str, Enum):
@@ -19,6 +19,10 @@ class Resolution(str, Enum):
19
19
  CANCEL = "CANCEL"
20
20
  MKT = "MKT"
21
21
 
22
+ @staticmethod
23
+ def from_bool(value: bool) -> "Resolution":
24
+ return Resolution.YES if value else Resolution.NO
25
+
22
26
 
23
27
  class TokenAmount(BaseModel):
24
28
  amount: float
@@ -57,6 +61,43 @@ class ResolvedBet(Bet):
57
61
  return f"Resolved bet for market {self.market_id} for question {self.market_question} created at {self.created_time}: {self.amount} on {self.outcome}. Bet was resolved at {self.resolved_time} and was {'correct' if self.is_correct else 'incorrect'}. Profit was {self.profit}"
58
62
 
59
63
 
64
+ class TokenAmountAndDirection(TokenAmount):
65
+ direction: bool
66
+
67
+
68
+ def to_boolean_outcome(value: str | bool) -> bool:
69
+ if isinstance(value, bool):
70
+ return value
71
+
72
+ elif isinstance(value, str):
73
+ value = value.lower().strip()
74
+
75
+ if value in {"true", "yes", "y", "1"}:
76
+ return True
77
+
78
+ elif value in {"false", "no", "n", "0"}:
79
+ return False
80
+
81
+ else:
82
+ raise ValueError(f"Expected a boolean string, but got {value}")
83
+
84
+ else:
85
+ raise ValueError(f"Expected a boolean or a string, but got {value}")
86
+
87
+
88
+ Decision = Annotated[bool, BeforeValidator(to_boolean_outcome)]
89
+
90
+
91
+ class ProbabilisticAnswer(BaseModel):
92
+ p_yes: Probability
93
+ confidence: float
94
+ reasoning: str | None = None
95
+
96
+ @property
97
+ def p_no(self) -> Probability:
98
+ return Probability(1 - self.p_yes)
99
+
100
+
60
101
  class Position(BaseModel):
61
102
  market_id: str
62
103
  amounts: dict[OutcomeStr, TokenAmount]
@@ -77,6 +77,10 @@ class Question(BaseModel):
77
77
  def opening_datetime(self) -> datetime:
78
78
  return datetime.fromtimestamp(self.openingTimestamp)
79
79
 
80
+ @property
81
+ def has_answer(self) -> bool:
82
+ return self.currentAnswer is not None
83
+
80
84
  @property
81
85
  def outcome_index(self) -> int | None:
82
86
  return (
@@ -88,6 +92,34 @@ class Question(BaseModel):
88
92
  else None
89
93
  )
90
94
 
95
+ @property
96
+ def is_binary(self) -> bool:
97
+ return len(self.outcomes) == 2
98
+
99
+ @property
100
+ def has_valid_answer(self) -> bool:
101
+ return self.outcome_index is not None and self.outcome_index != INVALID_ANSWER
102
+
103
+ @property
104
+ def boolean_outcome(self) -> bool:
105
+ if not self.is_binary:
106
+ raise ValueError(
107
+ f"Question with title {self.title} is not binary, it has {len(self.outcomes)} outcomes."
108
+ )
109
+
110
+ if not self.has_answer:
111
+ raise ValueError(f"Question with title {self.title} is not answered.")
112
+
113
+ outcome_index = check_not_none(self.outcome_index)
114
+
115
+ if not self.has_valid_answer:
116
+ raise ValueError(
117
+ f"Question with title {self.title} has invalid answer {outcome_index}."
118
+ )
119
+
120
+ outcome: str = self.outcomes[outcome_index]
121
+ return get_boolean_outcome(outcome)
122
+
91
123
 
92
124
  class OmenPosition(BaseModel):
93
125
  id: HexBytes
@@ -132,7 +164,15 @@ class OmenUserPosition(BaseModel):
132
164
 
133
165
  class OmenMarket(BaseModel):
134
166
  """
135
- https://aiomen.eth.limo
167
+ https://presagio.pages.dev
168
+
169
+ An Omen market goes through the following stages:
170
+
171
+ 1. creation - can add liquidty immediately, and trade immediately if there is liquidity
172
+ 2. closing - market is closed, and a question is simultaneously opened for answers on Reality
173
+ 3. finalizing - the question is finalized on reality (including any disputes)
174
+ 4. resolving - a manual step required by calling the Omen oracle contract
175
+ 5. redeeming - a user withdraws collateral tokens from the market
136
176
  """
137
177
 
138
178
  BET_AMOUNT_CURRENCY: t.ClassVar[Currency] = Currency.xDai
@@ -347,7 +387,7 @@ class OmenBet(BaseModel):
347
387
  collateralAmountUSD: USD
348
388
  feeAmount: Wei
349
389
  outcomeIndex: int
350
- outcomeTokensTraded: int
390
+ outcomeTokensTraded: Wei
351
391
  transactionHash: HexAddress
352
392
  fpmm: OmenMarket
353
393
 
@@ -372,7 +412,7 @@ class OmenBet(BaseModel):
372
412
  def get_profit(self) -> ProfitAmount:
373
413
  bet_amount_xdai = wei_to_xdai(self.collateralAmount)
374
414
  profit = (
375
- wei_to_xdai(Wei(self.outcomeTokensTraded)) - bet_amount_xdai
415
+ wei_to_xdai(self.outcomeTokensTraded) - bet_amount_xdai
376
416
  if self.boolean_outcome == self.fpmm.boolean_outcome
377
417
  else -bet_amount_xdai
378
418
  )
@@ -430,6 +470,8 @@ class RealityQuestion(BaseModel):
430
470
  updatedTimestamp: datetime
431
471
  contentHash: HexBytes
432
472
  questionId: HexBytes
473
+ answerFinalizedTimestamp: datetime
474
+ currentScheduledFinalizationTimestamp: datetime
433
475
 
434
476
  @property
435
477
  def url(self) -> str:
@@ -446,6 +488,32 @@ class RealityAnswer(BaseModel):
446
488
  createdBlock: int
447
489
 
448
490
 
491
+ class RealityResponse(BaseModel):
492
+ """
493
+ This is similar to `RealityAnswer`, but contains additional fields, most importantly `historyHash`.
494
+ """
495
+
496
+ id: str
497
+ timestamp: datetime
498
+ answer: HexBytes
499
+ isUnrevealed: bool
500
+ isCommitment: bool
501
+ bond: Wei
502
+ user: HexAddress
503
+ historyHash: HexBytes
504
+ question: RealityQuestion
505
+ createdBlock: int
506
+ revealedBlock: int | None
507
+
508
+ @property
509
+ def bond_xdai(self) -> xDai:
510
+ return wei_to_xdai(self.bond)
511
+
512
+ @property
513
+ def user_checksummed(self) -> ChecksumAddress:
514
+ return Web3.to_checksum_address(self.user)
515
+
516
+
449
517
  class RealityAnswers(BaseModel):
450
518
  answers: list[RealityAnswer]
451
519
 
@@ -1,7 +1,7 @@
1
1
  import os
2
2
  import random
3
3
  import typing as t
4
- from datetime import datetime
4
+ from datetime import datetime, timedelta
5
5
  from enum import Enum
6
6
 
7
7
  from web3 import Web3
@@ -521,6 +521,7 @@ class OmenRealitioContract(ContractOnGnosisChain):
521
521
  language: str,
522
522
  arbitrator: Arbitrator,
523
523
  opening: datetime,
524
+ timeout: timedelta = timedelta(days=1),
524
525
  nonce: int | None = None,
525
526
  tx_params: t.Optional[TxParams] = None,
526
527
  web3: Web3 | None = None,
@@ -547,7 +548,7 @@ class OmenRealitioContract(ContractOnGnosisChain):
547
548
  template_id=template_id,
548
549
  question=realitio_question,
549
550
  arbitrator=arbitrator_contract_address,
550
- timeout=86400, # See https://github.com/protofire/omen-exchange/blob/2cfdf6bfe37afa8b169731d51fea69d42321d66c/app/src/util/networks.ts#L278.
551
+ timeout=int(timeout.total_seconds()),
551
552
  opening_ts=int(opening.timestamp()),
552
553
  nonce=(
553
554
  nonce if nonce is not None else random.randint(0, 1000000)
@@ -33,7 +33,6 @@ from prediction_market_agent_tooling.markets.omen.omen_subgraph_handler import (
33
33
  from prediction_market_agent_tooling.markets.polymarket.utils import (
34
34
  find_resolution_on_polymarket,
35
35
  )
36
- from prediction_market_agent_tooling.tools.utils import check_not_none
37
36
  from prediction_market_agent_tooling.tools.web3_utils import ZERO_BYTES, xdai_to_wei
38
37
 
39
38
 
@@ -42,7 +41,6 @@ def claim_bonds_on_realitio_questions(
42
41
  questions: list[RealityQuestion],
43
42
  auto_withdraw: bool,
44
43
  web3: Web3 | None = None,
45
- silent_errors: bool = False,
46
44
  ) -> list[HexBytes]:
47
45
  claimed_questions: list[HexBytes] = []
48
46
 
@@ -50,19 +48,10 @@ def claim_bonds_on_realitio_questions(
50
48
  logger.info(
51
49
  f"[{idx+1} / {len(questions)}] Claiming bond for {question.questionId=} {question.url=}"
52
50
  )
53
- try:
54
- claim_bonds_on_realitio_question(
55
- api_keys, question, auto_withdraw=auto_withdraw, web3=web3
56
- )
57
- claimed_questions.append(question.questionId)
58
- except Exception as e:
59
- # TODO: This shouldn't be required once `claim_bonds_on_realitio_question` below is fixed.
60
- if silent_errors:
61
- logger.warning(
62
- f"Error while claiming bond for {question.questionId=} {question.url=}: {e}"
63
- )
64
- else:
65
- raise
51
+ claim_bonds_on_realitio_question(
52
+ api_keys, question, auto_withdraw=auto_withdraw, web3=web3
53
+ )
54
+ claimed_questions.append(question.questionId)
66
55
 
67
56
  return claimed_questions
68
57
 
@@ -77,50 +66,39 @@ def claim_bonds_on_realitio_question(
77
66
  realitio_contract = OmenRealitioContract()
78
67
 
79
68
  # Get all answers for the question.
80
- answers_objects = OmenSubgraphHandler().get_answers(question_id=question.questionId)
69
+ responses = OmenSubgraphHandler().get_responses(question_id=question.questionId)
81
70
 
82
- if not answers_objects:
83
- raise ValueError(f"No answers found for {question.questionId=}")
71
+ # They need to be processed in order.
72
+ responses = sorted(responses, key=lambda x: x.timestamp)
84
73
 
85
- if answers_objects[-1].question.historyHash == ZERO_BYTES:
86
- raise ValueError(f"Already claimed {question.questionId=}.")
74
+ if not responses:
75
+ raise ValueError(f"No answers found for {question.questionId.hex()=}")
87
76
 
88
- if len(answers_objects) > 1:
89
- # As you can see below, we need `history_hash` for every answer.
90
- # The trouble is, that historyHash is updated after each new answer and the contract holds only the latest one.
91
- # So if we have more than 1 answer, we missing the historyHash n-1 of them and this would fail.
92
- # You can find how to calculate history hash at https://realitio.github.io/docs/html/contract_explanation.html#answer-history-entries.
93
- # At the moment, we support only 1 answer, as for that one answer we will have the hash.
94
- raise NotImplementedError()
77
+ if responses[-1].question.historyHash == ZERO_BYTES:
78
+ raise ValueError(f"Already claimed {question.questionId.hex()=}.")
95
79
 
96
- # Logic taken from packages/valory/skills/decision_maker_abci/models.py in `def claim_params`.
97
80
  history_hashes: list[HexBytes] = []
98
81
  addresses: list[ChecksumAddress] = []
99
82
  bonds: list[Wei] = []
100
83
  answers: list[HexBytes] = []
101
84
 
102
- for i, answer in enumerate(reversed(answers_objects)):
103
- # history_hashes second-last-to-first, the hash of each history entry, calculated as described here:
104
- # https://realitio.github.io/docs/html/contract_explanation.html#answer-history-entries.
105
- if i == len(answers_objects) - 1:
85
+ # Caller must provide the answer history, in reverse order.
86
+ # See https://gnosisscan.io/address/0x79e32aE03fb27B07C89c0c568F80287C01ca2E57#code#L625 for the `claimWinnings` logic.
87
+ reversed_responses = list(reversed(responses))
88
+
89
+ for i, response in enumerate(reversed_responses):
90
+ # second-last-to-first, the hash of each history entry. (Final one should be empty).
91
+ if i == len(reversed_responses) - 1:
106
92
  history_hashes.append(ZERO_BYTES)
107
93
  else:
108
- # TODO: See `if len(answers_objects) > 1` above.
109
- # This is from the original Olas implementation (https://github.com/kongzii/trader/blob/700af475a4538cc3d5d22caf9dec9e9d22d72af1/packages/valory/skills/market_manager_abci/graph_tooling/requests.py#L297),
110
- # but it's most probably wrong (see comment above).
111
- history_hashes.append(
112
- check_not_none(
113
- answers_objects[i + 1].question.historyHash,
114
- "Shouldn't be None here.",
115
- )
116
- )
94
+ history_hashes.append(reversed_responses[i + 1].historyHash)
117
95
 
118
96
  # last-to-first, the address of each answerer or commitment sender
119
- addresses.append(Web3.to_checksum_address(answer.question.user))
97
+ addresses.append(Web3.to_checksum_address(response.user))
120
98
  # last-to-first, the bond supplied with each answer or commitment
121
- bonds.append(answer.lastBond)
99
+ bonds.append(response.bond)
122
100
  # last-to-first, each answer supplied, or commitment ID if the answer was supplied with commit->reveal
123
- answers.append(answer.answer)
101
+ answers.append(response.answer)
124
102
 
125
103
  realitio_contract.claimWinnings(
126
104
  api_keys=api_keys,