prediction-market-agent-tooling 0.64.12.dev659__py3-none-any.whl → 0.65.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.
Files changed (53) hide show
  1. prediction_market_agent_tooling/benchmark/agents.py +19 -16
  2. prediction_market_agent_tooling/benchmark/benchmark.py +94 -84
  3. prediction_market_agent_tooling/benchmark/utils.py +8 -9
  4. prediction_market_agent_tooling/chains.py +4 -0
  5. prediction_market_agent_tooling/config.py +4 -3
  6. prediction_market_agent_tooling/deploy/agent.py +85 -125
  7. prediction_market_agent_tooling/deploy/agent_example.py +20 -10
  8. prediction_market_agent_tooling/deploy/betting_strategy.py +222 -96
  9. prediction_market_agent_tooling/deploy/constants.py +4 -0
  10. prediction_market_agent_tooling/jobs/jobs_models.py +15 -4
  11. prediction_market_agent_tooling/jobs/omen/omen_jobs.py +3 -3
  12. prediction_market_agent_tooling/markets/agent_market.py +145 -50
  13. prediction_market_agent_tooling/markets/blockchain_utils.py +10 -1
  14. prediction_market_agent_tooling/markets/data_models.py +83 -17
  15. prediction_market_agent_tooling/markets/manifold/api.py +18 -7
  16. prediction_market_agent_tooling/markets/manifold/data_models.py +23 -16
  17. prediction_market_agent_tooling/markets/manifold/manifold.py +18 -18
  18. prediction_market_agent_tooling/markets/manifold/utils.py +7 -12
  19. prediction_market_agent_tooling/markets/markets.py +2 -1
  20. prediction_market_agent_tooling/markets/metaculus/metaculus.py +29 -4
  21. prediction_market_agent_tooling/markets/omen/data_models.py +17 -32
  22. prediction_market_agent_tooling/markets/omen/omen.py +65 -108
  23. prediction_market_agent_tooling/markets/omen/omen_contracts.py +2 -5
  24. prediction_market_agent_tooling/markets/omen/omen_resolving.py +13 -13
  25. prediction_market_agent_tooling/markets/omen/omen_subgraph_handler.py +18 -12
  26. prediction_market_agent_tooling/markets/polymarket/data_models.py +7 -3
  27. prediction_market_agent_tooling/markets/polymarket/data_models_web.py +7 -3
  28. prediction_market_agent_tooling/markets/polymarket/polymarket.py +5 -4
  29. prediction_market_agent_tooling/markets/seer/data_models.py +0 -83
  30. prediction_market_agent_tooling/markets/seer/price_manager.py +44 -30
  31. prediction_market_agent_tooling/markets/seer/seer.py +105 -105
  32. prediction_market_agent_tooling/markets/seer/seer_subgraph_handler.py +34 -41
  33. prediction_market_agent_tooling/tools/betting_strategies/kelly_criterion.py +1 -1
  34. prediction_market_agent_tooling/tools/cow/cow_order.py +10 -3
  35. prediction_market_agent_tooling/tools/is_predictable.py +2 -3
  36. prediction_market_agent_tooling/tools/langfuse_client_utils.py +4 -4
  37. prediction_market_agent_tooling/tools/omen/sell_positions.py +3 -2
  38. prediction_market_agent_tooling/tools/utils.py +26 -13
  39. {prediction_market_agent_tooling-0.64.12.dev659.dist-info → prediction_market_agent_tooling-0.65.0.dist-info}/METADATA +2 -2
  40. {prediction_market_agent_tooling-0.64.12.dev659.dist-info → prediction_market_agent_tooling-0.65.0.dist-info}/RECORD +43 -52
  41. prediction_market_agent_tooling/monitor/financial_metrics/financial_metrics.py +0 -68
  42. prediction_market_agent_tooling/monitor/markets/manifold.py +0 -90
  43. prediction_market_agent_tooling/monitor/markets/metaculus.py +0 -43
  44. prediction_market_agent_tooling/monitor/markets/omen.py +0 -88
  45. prediction_market_agent_tooling/monitor/markets/polymarket.py +0 -49
  46. prediction_market_agent_tooling/monitor/monitor.py +0 -406
  47. prediction_market_agent_tooling/monitor/monitor_app.py +0 -149
  48. prediction_market_agent_tooling/monitor/monitor_settings.py +0 -27
  49. prediction_market_agent_tooling/tools/betting_strategies/market_moving.py +0 -146
  50. prediction_market_agent_tooling/tools/betting_strategies/minimum_bet_to_win.py +0 -12
  51. {prediction_market_agent_tooling-0.64.12.dev659.dist-info → prediction_market_agent_tooling-0.65.0.dist-info}/LICENSE +0 -0
  52. {prediction_market_agent_tooling-0.64.12.dev659.dist-info → prediction_market_agent_tooling-0.65.0.dist-info}/WHEEL +0 -0
  53. {prediction_market_agent_tooling-0.64.12.dev659.dist-info → prediction_market_agent_tooling-0.65.0.dist-info}/entry_points.txt +0 -0
@@ -1,7 +1,4 @@
1
1
  import getpass
2
- import inspect
3
- import os
4
- import tempfile
5
2
  import time
6
3
  import typing as t
7
4
  from datetime import timedelta
@@ -13,19 +10,9 @@ from pydantic import computed_field
13
10
  from prediction_market_agent_tooling.config import APIKeys
14
11
  from prediction_market_agent_tooling.deploy.betting_strategy import (
15
12
  BettingStrategy,
16
- MaxAccuracyBettingStrategy,
13
+ MultiCategoricalMaxAccuracyBettingStrategy,
17
14
  TradeType,
18
15
  )
19
- from prediction_market_agent_tooling.deploy.constants import (
20
- MARKET_TYPE_KEY,
21
- REPOSITORY_KEY,
22
- )
23
- from prediction_market_agent_tooling.deploy.gcp.deploy import (
24
- deploy_to_gcp,
25
- run_deployed_gcp_function,
26
- schedule_deployed_gcp_function,
27
- )
28
- from prediction_market_agent_tooling.deploy.gcp.utils import gcp_function_is_active
29
16
  from prediction_market_agent_tooling.deploy.trade_interval import (
30
17
  FixedInterval,
31
18
  TradeInterval,
@@ -40,6 +27,7 @@ from prediction_market_agent_tooling.markets.agent_market import (
40
27
  SortBy,
41
28
  )
42
29
  from prediction_market_agent_tooling.markets.data_models import (
30
+ CategoricalProbabilisticAnswer,
43
31
  ExistingPosition,
44
32
  PlacedTrade,
45
33
  ProbabilisticAnswer,
@@ -52,9 +40,6 @@ from prediction_market_agent_tooling.markets.markets import (
52
40
  from prediction_market_agent_tooling.markets.omen.omen import (
53
41
  send_keeping_token_to_eoa_xdai,
54
42
  )
55
- from prediction_market_agent_tooling.monitor.monitor_app import (
56
- MARKET_TYPE_TO_DEPLOYED_AGENT,
57
- )
58
43
  from prediction_market_agent_tooling.tools.custom_exceptions import (
59
44
  CantPayForGasError,
60
45
  OutOfFundsError,
@@ -185,88 +170,6 @@ class DeployableAgent:
185
170
  self.run(market_type=market_type)
186
171
  time.sleep(sleep_time)
187
172
 
188
- def deploy_gcp(
189
- self,
190
- repository: str,
191
- market_type: MarketType,
192
- api_keys: APIKeys,
193
- memory: int,
194
- labels: dict[str, str] | None = None,
195
- env_vars: dict[str, str] | None = None,
196
- secrets: dict[str, str] | None = None,
197
- cron_schedule: str | None = None,
198
- gcp_fname: str | None = None,
199
- start_time: DatetimeUTC | None = None,
200
- timeout: int = 180,
201
- ) -> None:
202
- """
203
- Deploy the agent as GCP Function.
204
- """
205
- path_to_agent_file = os.path.relpath(inspect.getfile(self.__class__))
206
-
207
- entrypoint_function_name = "main"
208
- entrypoint_template = f"""
209
- from {path_to_agent_file.replace("/", ".").replace(".py", "")} import *
210
- import functions_framework
211
- from prediction_market_agent_tooling.markets.markets import MarketType
212
-
213
- @functions_framework.http
214
- def {entrypoint_function_name}(request) -> str:
215
- {self.__class__.__name__}().run(market_type={market_type.__class__.__name__}.{market_type.name})
216
- return "Success"
217
- """
218
-
219
- gcp_fname = gcp_fname or self.get_gcloud_fname(market_type)
220
-
221
- # For labels, only hyphens (-), underscores (_), lowercase characters, and numbers are allowed in values.
222
- labels = (labels or {}) | {
223
- MARKET_TYPE_KEY: market_type.value,
224
- }
225
- env_vars = (env_vars or {}) | {
226
- REPOSITORY_KEY: repository,
227
- }
228
- secrets = secrets or {}
229
-
230
- env_vars |= api_keys.model_dump_public()
231
- secrets |= api_keys.model_dump_secrets()
232
-
233
- monitor_agent = MARKET_TYPE_TO_DEPLOYED_AGENT[market_type].from_api_keys(
234
- name=gcp_fname,
235
- start_time=start_time or utcnow(),
236
- api_keys=api_keys,
237
- )
238
- env_vars |= monitor_agent.model_dump_prefixed()
239
-
240
- with tempfile.NamedTemporaryFile(mode="w", suffix=".py") as f:
241
- f.write(entrypoint_template)
242
- f.flush()
243
-
244
- fname = deploy_to_gcp(
245
- gcp_fname=gcp_fname,
246
- requirements_file=None,
247
- extra_deps=[repository],
248
- function_file=f.name,
249
- labels=labels,
250
- env_vars=env_vars,
251
- secrets=secrets,
252
- memory=memory,
253
- entrypoint_function_name=entrypoint_function_name,
254
- timeout=timeout,
255
- )
256
-
257
- # Check that the function is deployed
258
- if not gcp_function_is_active(fname):
259
- raise RuntimeError("Failed to deploy the function")
260
-
261
- # Run the function
262
- response = run_deployed_gcp_function(fname)
263
- if not response.ok:
264
- raise RuntimeError("Failed to run the deployed function")
265
-
266
- # Schedule the function
267
- if cron_schedule:
268
- schedule_deployed_gcp_function(fname, cron_schedule=cron_schedule)
269
-
270
173
  def run(self, market_type: MarketType) -> None:
271
174
  """
272
175
  Run single iteration of the agent.
@@ -292,6 +195,7 @@ class DeployablePredictionAgent(DeployableAgent):
292
195
  n_markets_to_fetch: int = MAX_AVAILABLE_MARKETS
293
196
  trade_on_markets_created_after: DatetimeUTC | None = None
294
197
  get_markets_sort_by: SortBy = SortBy.CLOSING_SOONEST
198
+ get_markets_filter_by: FilterBy = FilterBy.OPEN
295
199
 
296
200
  # Agent behaviour when filtering fetched markets
297
201
  allow_invalid_questions: bool = False
@@ -377,9 +281,13 @@ class DeployablePredictionAgent(DeployableAgent):
377
281
  return False
378
282
 
379
283
  # Manifold allows to bet only on markets with probability between 1 and 99.
380
- if market_type == MarketType.MANIFOLD and not (1 < market.current_p_yes < 99):
381
- logger.info("Manifold's market probability not in the range 1-99.")
382
- return False
284
+ if market_type == MarketType.MANIFOLD:
285
+ probability_yes = market.probabilities[
286
+ market.get_outcome_str_from_bool(True)
287
+ ]
288
+ if not probability_yes or not 1 < probability_yes < 99:
289
+ logger.info("Manifold's market probability not in the range 1-99.")
290
+ return False
383
291
 
384
292
  # Do as a last check, as it uses paid OpenAI API.
385
293
  if not is_predictable_binary(market.question):
@@ -392,11 +300,32 @@ class DeployablePredictionAgent(DeployableAgent):
392
300
 
393
301
  return True
394
302
 
303
+ def answer_categorical_market(
304
+ self, market: AgentMarket
305
+ ) -> CategoricalProbabilisticAnswer | None:
306
+ raise NotImplementedError("This method must be implemented by the subclass")
307
+
395
308
  def answer_binary_market(self, market: AgentMarket) -> ProbabilisticAnswer | None:
396
309
  """
397
- Answer the binary market. This method must be implemented by the subclass.
310
+ Answer the binary market.
311
+
312
+ If this method is not overridden by the subclass, it will fall back to using
313
+ answer_categorical_market(). Therefore, subclasses only need to implement
314
+ answer_categorical_market() if they want to handle both types of markets.
398
315
  """
399
- raise NotImplementedError("This method must be implemented by the subclass")
316
+ raise NotImplementedError(
317
+ "Either this method, or answer_categorical_market, must be implemented by the subclass."
318
+ )
319
+
320
+ @property
321
+ def fetch_categorical_markets(self) -> bool:
322
+ # Check if the subclass has implemented the answer_categorical_market method, if yes, fetch categorical markets as well.
323
+ if (
324
+ self.answer_categorical_market.__func__ # type: ignore[attr-defined] # This works just fine, but mypy doesn't know about it for some reason.
325
+ is not DeployablePredictionAgent.answer_categorical_market
326
+ ):
327
+ return True
328
+ return False
400
329
 
401
330
  def get_markets(
402
331
  self,
@@ -407,11 +336,12 @@ class DeployablePredictionAgent(DeployableAgent):
407
336
  """
408
337
  cls = market_type.market_class
409
338
  # Fetch the soonest closing markets to choose from
410
- available_markets = cls.get_binary_markets(
339
+ available_markets = cls.get_markets(
411
340
  limit=self.n_markets_to_fetch,
412
341
  sort_by=self.get_markets_sort_by,
413
- filter_by=FilterBy.OPEN,
342
+ filter_by=self.get_markets_filter_by,
414
343
  created_after=self.trade_on_markets_created_after,
344
+ fetch_categorical_markets=self.fetch_categorical_markets,
415
345
  )
416
346
  return available_markets
417
347
 
@@ -432,6 +362,35 @@ class DeployablePredictionAgent(DeployableAgent):
432
362
  multiplier=3,
433
363
  )
434
364
 
365
+ def build_answer(
366
+ self,
367
+ market_type: MarketType,
368
+ market: AgentMarket,
369
+ verify_market: bool = True,
370
+ ) -> CategoricalProbabilisticAnswer | None:
371
+ if verify_market and not self.verify_market(market_type, market):
372
+ logger.info(f"Market '{market.question}' doesn't meet the criteria.")
373
+ return None
374
+
375
+ logger.info(f"Answering market '{market.question}'.")
376
+
377
+ if market.is_binary:
378
+ try:
379
+ binary_answer = self.answer_binary_market(market)
380
+ return (
381
+ CategoricalProbabilisticAnswer.from_probabilistic_answer(
382
+ binary_answer
383
+ )
384
+ if binary_answer is not None
385
+ else None
386
+ )
387
+ except NotImplementedError:
388
+ logger.info(
389
+ "answer_binary_market() not implemented, falling back to answer_categorical_market()"
390
+ )
391
+
392
+ return self.answer_categorical_market(market)
393
+
435
394
  def process_market(
436
395
  self,
437
396
  market_type: MarketType,
@@ -443,13 +402,9 @@ class DeployablePredictionAgent(DeployableAgent):
443
402
  f"Processing market {market.question=} from {market.url=} with liquidity {market.get_liquidity()}."
444
403
  )
445
404
 
446
- answer: ProbabilisticAnswer | None
447
- if verify_market and not self.verify_market(market_type, market):
448
- logger.info(f"Market '{market.question}' doesn't meet the criteria.")
449
- answer = None
450
- else:
451
- logger.info(f"Answering market '{market.question}'.")
452
- answer = self.answer_binary_market(market)
405
+ answer = self.build_answer(
406
+ market=market, market_type=market_type, verify_market=verify_market
407
+ )
453
408
 
454
409
  processed_market = (
455
410
  ProcessedMarket(answer=answer) if answer is not None else None
@@ -494,6 +449,7 @@ class DeployablePredictionAgent(DeployableAgent):
494
449
  """
495
450
  logger.info("Start processing of markets.")
496
451
  available_markets = self.get_markets(market_type)
452
+
497
453
  logger.info(
498
454
  f"Fetched {len(available_markets)=} markets to process, going to process {self.bet_on_n_markets_per_run=}."
499
455
  )
@@ -580,24 +536,29 @@ class DeployableTraderAgent(DeployablePredictionAgent):
580
536
  f"Minimum required balance {min_required_balance_to_trade} for agent {api_keys.bet_from_address=} is not met."
581
537
  )
582
538
 
539
+ @staticmethod
540
+ def get_total_amount_to_bet(market: AgentMarket) -> USD:
541
+ user_id = market.get_user_id(api_keys=APIKeys())
542
+
543
+ total_amount = market.get_in_usd(market.get_tiny_bet_amount())
544
+ existing_position = market.get_position(user_id=user_id)
545
+ if existing_position and existing_position.total_amount_current > USD(0):
546
+ total_amount += existing_position.total_amount_current
547
+ return total_amount
548
+
583
549
  def get_betting_strategy(self, market: AgentMarket) -> BettingStrategy:
584
550
  """
585
551
  Override this method to customize betting strategy of your agent.
586
552
 
587
553
  Given the market and prediction, agent uses this method to calculate optimal outcome and bet size.
588
554
  """
589
- user_id = market.get_user_id(api_keys=APIKeys())
590
-
591
- total_amount = market.get_in_usd(market.get_tiny_bet_amount())
592
- if existing_position := market.get_position(user_id=user_id):
593
- total_amount += existing_position.total_amount_current
594
-
595
- return MaxAccuracyBettingStrategy(bet_amount=total_amount)
555
+ total_amount = self.get_total_amount_to_bet(market)
556
+ return MultiCategoricalMaxAccuracyBettingStrategy(bet_amount=total_amount)
596
557
 
597
558
  def build_trades(
598
559
  self,
599
560
  market: AgentMarket,
600
- answer: ProbabilisticAnswer,
561
+ answer: CategoricalProbabilisticAnswer,
601
562
  existing_position: ExistingPosition | None,
602
563
  ) -> list[Trade]:
603
564
  strategy = self.get_betting_strategy(market=market)
@@ -654,8 +615,9 @@ class DeployableTraderAgent(DeployablePredictionAgent):
654
615
  market.get_position(user_id),
655
616
  "Should exists if we are going to sell outcomes.",
656
617
  )
618
+
657
619
  current_position_value_usd = current_position.amounts_current[
658
- market.get_outcome_str_from_bool(trade.outcome)
620
+ trade.outcome
659
621
  ]
660
622
  amount_to_sell: USD | OutcomeToken
661
623
  if current_position_value_usd <= trade.amount:
@@ -663,9 +625,7 @@ class DeployableTraderAgent(DeployablePredictionAgent):
663
625
  f"Current value of position {trade.outcome=}, {current_position_value_usd=} is less than the desired selling amount {trade.amount=}. Selling all."
664
626
  )
665
627
  # In case the agent asked to sell too much, provide the amount to sell as all outcome tokens, instead of in USD, to minimze fx fluctuations when selling.
666
- amount_to_sell = current_position.amounts_ot[
667
- market.get_outcome_str_from_bool(trade.outcome)
668
- ]
628
+ amount_to_sell = current_position.amounts_ot[trade.outcome]
669
629
  else:
670
630
  amount_to_sell = trade.amount
671
631
  id = market.sell_tokens(
@@ -1,27 +1,37 @@
1
1
  import random
2
2
 
3
- from prediction_market_agent_tooling.deploy.agent import (
4
- DeployableTraderAgent,
5
- ProbabilisticAnswer,
6
- )
3
+ from prediction_market_agent_tooling.deploy.agent import DeployableTraderAgent
7
4
  from prediction_market_agent_tooling.gtypes import Probability
8
- from prediction_market_agent_tooling.markets.agent_market import AgentMarket
5
+ from prediction_market_agent_tooling.markets.agent_market import AgentMarket, SortBy
6
+ from prediction_market_agent_tooling.markets.data_models import (
7
+ CategoricalProbabilisticAnswer,
8
+ )
9
9
  from prediction_market_agent_tooling.markets.markets import MarketType
10
10
 
11
11
 
12
12
  class DeployableCoinFlipAgent(DeployableTraderAgent):
13
+ get_markets_sort_by = SortBy.HIGHEST_LIQUIDITY
14
+
13
15
  def verify_market(self, market_type: MarketType, market: AgentMarket) -> bool:
14
16
  return True
15
17
 
16
- def answer_binary_market(self, market: AgentMarket) -> ProbabilisticAnswer | None:
17
- decision = random.choice([True, False])
18
- return ProbabilisticAnswer(
19
- p_yes=Probability(float(decision)),
18
+ def answer_categorical_market(
19
+ self, market: AgentMarket
20
+ ) -> CategoricalProbabilisticAnswer:
21
+ decision = random.choice(market.outcomes)
22
+ probabilities = {decision: Probability(1.0)}
23
+ for outcome in market.outcomes:
24
+ if outcome != decision:
25
+ probabilities[outcome] = Probability(0.0)
26
+ return CategoricalProbabilisticAnswer(
27
+ probabilities=probabilities,
20
28
  confidence=0.5,
21
29
  reasoning="I flipped a coin to decide.",
22
30
  )
23
31
 
24
32
 
25
33
  class DeployableAlwaysRaiseAgent(DeployableTraderAgent):
26
- def answer_binary_market(self, market: AgentMarket) -> ProbabilisticAnswer | None:
34
+ def answer_categorical_market(
35
+ self, market: AgentMarket
36
+ ) -> CategoricalProbabilisticAnswer | None:
27
37
  raise RuntimeError("I always raise!")