prediction-market-agent-tooling 0.64.12.dev660__py3-none-any.whl → 0.65.1__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/config.py +28 -7
  5. prediction_market_agent_tooling/deploy/agent.py +85 -125
  6. prediction_market_agent_tooling/deploy/agent_example.py +20 -10
  7. prediction_market_agent_tooling/deploy/betting_strategy.py +222 -96
  8. prediction_market_agent_tooling/deploy/constants.py +4 -0
  9. prediction_market_agent_tooling/jobs/jobs_models.py +15 -4
  10. prediction_market_agent_tooling/jobs/omen/omen_jobs.py +3 -3
  11. prediction_market_agent_tooling/loggers.py +1 -0
  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.dev660.dist-info → prediction_market_agent_tooling-0.65.1.dist-info}/METADATA +2 -2
  40. {prediction_market_agent_tooling-0.64.12.dev660.dist-info → prediction_market_agent_tooling-0.65.1.dist-info}/RECORD +43 -53
  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.dev660.dist-info → prediction_market_agent_tooling-0.65.1.dist-info}/LICENSE +0 -0
  52. {prediction_market_agent_tooling-0.64.12.dev660.dist-info → prediction_market_agent_tooling-0.65.1.dist-info}/WHEEL +0 -0
  53. {prediction_market_agent_tooling-0.64.12.dev660.dist-info → prediction_market_agent_tooling-0.65.1.dist-info}/entry_points.txt +0 -0
@@ -1,7 +1,9 @@
1
1
  import typing as t
2
+ from collections import defaultdict
2
3
  from datetime import timedelta
3
4
 
4
5
  import tenacity
6
+ from tqdm import tqdm
5
7
  from web3 import Web3
6
8
 
7
9
  from prediction_market_agent_tooling.config import APIKeys
@@ -43,8 +45,6 @@ from prediction_market_agent_tooling.markets.omen.data_models import (
43
45
  OmenBet,
44
46
  OmenMarket,
45
47
  OmenUserPosition,
46
- get_bet_outcome,
47
- get_boolean_outcome,
48
48
  )
49
49
  from prediction_market_agent_tooling.markets.omen.omen_contracts import (
50
50
  OMEN_DEFAULT_MARKET_FEE_PERC,
@@ -59,7 +59,6 @@ from prediction_market_agent_tooling.markets.omen.omen_contracts import (
59
59
  build_parent_collection_id,
60
60
  )
61
61
  from prediction_market_agent_tooling.markets.omen.omen_subgraph_handler import (
62
- SAFE_COLLATERAL_TOKENS_ADDRESSES,
63
62
  OmenSubgraphHandler,
64
63
  )
65
64
  from prediction_market_agent_tooling.tools.balances import get_balances
@@ -160,14 +159,14 @@ class OmenAgentMarket(AgentMarket):
160
159
 
161
160
  def liquidate_existing_positions(
162
161
  self,
163
- bet_outcome: bool,
162
+ bet_outcome: OutcomeStr,
164
163
  web3: Web3 | None = None,
165
164
  api_keys: APIKeys | None = None,
166
165
  larger_than: OutcomeToken | None = None,
167
166
  ) -> None:
168
167
  """
169
168
  Liquidates all previously existing positions.
170
- Returns the amount in collateral obtained by selling the positions.
169
+
171
170
  """
172
171
  api_keys = api_keys if api_keys is not None else APIKeys()
173
172
  better_address = api_keys.bet_from_address
@@ -180,10 +179,9 @@ class OmenAgentMarket(AgentMarket):
180
179
 
181
180
  for prev_position in prev_positions_for_market:
182
181
  for position_outcome, token_amount in prev_position.amounts_ot.items():
183
- position_outcome_bool = get_boolean_outcome(position_outcome)
184
- if position_outcome_bool != bet_outcome:
182
+ if position_outcome != bet_outcome:
185
183
  self.sell_tokens(
186
- outcome=position_outcome_bool,
184
+ outcome=position_outcome,
187
185
  amount=token_amount,
188
186
  auto_withdraw=True,
189
187
  web3=web3,
@@ -192,7 +190,7 @@ class OmenAgentMarket(AgentMarket):
192
190
 
193
191
  def place_bet(
194
192
  self,
195
- outcome: bool,
193
+ outcome: OutcomeStr,
196
194
  amount: USD,
197
195
  auto_deposit: bool = True,
198
196
  web3: Web3 | None = None,
@@ -206,14 +204,14 @@ class OmenAgentMarket(AgentMarket):
206
204
  api_keys=api_keys if api_keys is not None else APIKeys(),
207
205
  amount=amount,
208
206
  market=self,
209
- binary_outcome=outcome,
207
+ outcome=outcome,
210
208
  auto_deposit=auto_deposit,
211
209
  web3=web3,
212
210
  )
213
211
 
214
212
  def buy_tokens(
215
213
  self,
216
- outcome: bool,
214
+ outcome: OutcomeStr,
217
215
  amount: USD,
218
216
  web3: Web3 | None = None,
219
217
  api_keys: APIKeys | None = None,
@@ -226,7 +224,7 @@ class OmenAgentMarket(AgentMarket):
226
224
  )
227
225
 
228
226
  def get_sell_value_of_outcome_token(
229
- self, outcome: str, amount: OutcomeToken, web3: Web3 | None = None
227
+ self, outcome: OutcomeStr, amount: OutcomeToken, web3: Web3 | None = None
230
228
  ) -> CollateralToken:
231
229
  """
232
230
  Market can have as collateral token GNO for example.
@@ -234,19 +232,15 @@ class OmenAgentMarket(AgentMarket):
234
232
  When selling, you need to provide the amount in GNO, which is cumbersome because you know how much shares you have, but you don't have the price of the shares in GNO.
235
233
  Use this to convert how much collateral token (GNO in our example) to sell, to get the amount of shares you want to sell.
236
234
  """
237
- outcome_bool = get_boolean_outcome(outcome)
238
235
 
239
236
  pool_balance = get_conditional_tokens_balance_for_market(
240
237
  self, self.market_maker_contract_address_checksummed, web3=web3
241
238
  )
242
-
243
- sell_str = self.outcomes[self.yes_index if outcome_bool else self.no_index]
244
- other_str = self.outcomes[self.no_index if outcome_bool else self.yes_index]
245
-
239
+ outcome_idx = self.index_set_to_outcome_index(self.get_index_set(outcome))
246
240
  collateral = calculate_sell_amount_in_collateral(
247
241
  shares_to_sell=amount,
248
- holdings=pool_balance[self.get_index_set(sell_str)].as_outcome_token,
249
- other_holdings=pool_balance[self.get_index_set(other_str)].as_outcome_token,
242
+ outcome_index=outcome_idx,
243
+ pool_balances=[x.as_outcome_token for x in pool_balance.values()],
250
244
  fees=self.fees,
251
245
  )
252
246
 
@@ -254,7 +248,7 @@ class OmenAgentMarket(AgentMarket):
254
248
 
255
249
  def sell_tokens(
256
250
  self,
257
- outcome: bool,
251
+ outcome: OutcomeStr,
258
252
  amount: USD | OutcomeToken,
259
253
  auto_withdraw: bool = True,
260
254
  api_keys: APIKeys | None = None,
@@ -268,7 +262,7 @@ class OmenAgentMarket(AgentMarket):
268
262
  amount=amount,
269
263
  api_keys=api_keys if api_keys is not None else APIKeys(),
270
264
  market=self,
271
- binary_outcome=outcome,
265
+ outcome=outcome,
272
266
  auto_withdraw=auto_withdraw,
273
267
  web3=web3,
274
268
  )
@@ -347,7 +341,6 @@ class OmenAgentMarket(AgentMarket):
347
341
  resolution=model.get_resolution_enum(),
348
342
  created_time=model.creation_datetime,
349
343
  finalized_time=model.finalized_datetime,
350
- current_p_yes=model.current_p_yes,
351
344
  condition=model.condition,
352
345
  url=model.url,
353
346
  volume=model.collateralVolume.as_token,
@@ -362,24 +355,30 @@ class OmenAgentMarket(AgentMarket):
362
355
  model.outcomes[i]: model.outcomeTokenAmounts[i].as_outcome_token
363
356
  for i in range(len(model.outcomes))
364
357
  },
358
+ probabilities=AgentMarket.build_probability_map(
359
+ outcome_token_amounts=model.outcomeTokenAmounts,
360
+ outcomes=list(model.outcomes),
361
+ ),
365
362
  )
366
363
 
367
364
  @staticmethod
368
- def get_binary_markets(
365
+ def get_markets(
369
366
  limit: int,
370
367
  sort_by: SortBy,
371
368
  filter_by: FilterBy = FilterBy.OPEN,
372
369
  created_after: t.Optional[DatetimeUTC] = None,
373
370
  excluded_questions: set[str] | None = None,
371
+ fetch_categorical_markets: bool = False,
374
372
  ) -> t.Sequence["OmenAgentMarket"]:
375
373
  return [
376
374
  OmenAgentMarket.from_data_model(m)
377
- for m in OmenSubgraphHandler().get_omen_binary_markets_simple(
375
+ for m in OmenSubgraphHandler().get_omen_markets_simple(
378
376
  limit=limit,
379
377
  sort_by=sort_by,
380
378
  filter_by=filter_by,
381
379
  created_after=created_after,
382
380
  excluded_questions=excluded_questions,
381
+ include_categorical_markets=fetch_categorical_markets,
383
382
  )
384
383
  ]
385
384
 
@@ -471,7 +470,7 @@ class OmenAgentMarket(AgentMarket):
471
470
  address=self.market_maker_contract_address_checksummed,
472
471
  )
473
472
 
474
- def get_index_set(self, outcome: str) -> int:
473
+ def get_index_set(self, outcome: OutcomeStr) -> int:
475
474
  return self.get_outcome_index(outcome) + 1
476
475
 
477
476
  def index_set_to_outcome_index(cls, index_set: int) -> int:
@@ -482,13 +481,14 @@ class OmenAgentMarket(AgentMarket):
482
481
  cls.get_outcome_str(cls.index_set_to_outcome_index(index_set))
483
482
  )
484
483
 
485
- def get_outcome_str_from_bool(self, outcome: bool) -> OutcomeStr:
484
+ @staticmethod
485
+ def get_outcome_str_from_bool(outcome: bool) -> OutcomeStr:
486
486
  return (
487
487
  OutcomeStr(OMEN_TRUE_OUTCOME) if outcome else OutcomeStr(OMEN_FALSE_OUTCOME)
488
488
  )
489
489
 
490
490
  def get_token_balance(
491
- self, user_id: str, outcome: str, web3: Web3 | None = None
491
+ self, user_id: str, outcome: OutcomeStr, web3: Web3 | None = None
492
492
  ) -> OutcomeToken:
493
493
  index_set = self.get_index_set(outcome)
494
494
  balances = get_conditional_tokens_balance_for_market(
@@ -522,16 +522,20 @@ class OmenAgentMarket(AgentMarket):
522
522
  )
523
523
 
524
524
  # Sort positions and corresponding markets by condition_id
525
- omen_positions_dict: dict[HexBytes, list[OmenUserPosition]] = {}
525
+ omen_positions_dict: dict[HexBytes, list[OmenUserPosition]] = defaultdict(list)
526
+
526
527
  for omen_position in omen_positions:
527
- condition_id = omen_position.position.condition_id
528
- omen_positions_dict.setdefault(condition_id, []).append(omen_position)
528
+ omen_positions_dict[omen_position.position.condition_id].append(
529
+ omen_position
530
+ )
529
531
 
532
+ # We include categorical markets below simply because we are already filtering on condition_ids.
530
533
  omen_markets: dict[HexBytes, OmenMarket] = {
531
534
  m.condition.id: m
532
- for m in sgh.get_omen_binary_markets(
535
+ for m in sgh.get_omen_markets(
533
536
  limit=None,
534
537
  condition_id_in=list(omen_positions_dict.keys()),
538
+ include_categorical_markets=True,
535
539
  )
536
540
  }
537
541
 
@@ -545,7 +549,9 @@ class OmenAgentMarket(AgentMarket):
545
549
  )
546
550
 
547
551
  positions = []
548
- for condition_id, omen_positions in omen_positions_dict.items():
552
+ for condition_id, omen_positions in tqdm(
553
+ omen_positions_dict.items(), mininterval=3
554
+ ):
549
555
  market = cls.from_data_model(omen_markets[condition_id])
550
556
 
551
557
  # Skip markets that cannot be traded if `liquid_only`` is True.
@@ -595,7 +601,7 @@ class OmenAgentMarket(AgentMarket):
595
601
  return get_omen_user_url(keys.bet_from_address)
596
602
 
597
603
  def get_buy_token_amount(
598
- self, bet_amount: USD | CollateralToken, direction: bool
604
+ self, bet_amount: USD | CollateralToken, outcome: OutcomeStr
599
605
  ) -> OutcomeToken:
600
606
  """
601
607
  Note: this is only valid if the market instance's token pool is
@@ -604,63 +610,26 @@ class OmenAgentMarket(AgentMarket):
604
610
  outcome_token_pool = check_not_none(self.outcome_token_pool)
605
611
  amount = get_buy_outcome_token_amount(
606
612
  investment_amount=self.get_in_token(bet_amount),
607
- buy_direction=direction,
608
- yes_outcome_pool_size=outcome_token_pool[OMEN_TRUE_OUTCOME],
609
- no_outcome_pool_size=outcome_token_pool[OMEN_FALSE_OUTCOME],
613
+ outcome_index=self.get_outcome_index(outcome),
614
+ pool_balances=[outcome_token_pool[x] for x in self.outcomes],
610
615
  fees=self.fees,
611
616
  )
612
617
  return amount
613
618
 
614
619
  def _get_buy_token_amount_from_smart_contract(
615
- self, bet_amount: USD, direction: bool
620
+ self, bet_amount: USD, outcome: OutcomeStr
616
621
  ) -> OutcomeToken:
617
622
  bet_amount_in_tokens = get_usd_in_token(
618
623
  bet_amount, self.collateral_token_contract_address_checksummed
619
624
  )
625
+
620
626
  received_token_amount_wei = self.get_contract().calcBuyAmount(
621
627
  investment_amount=bet_amount_in_tokens.as_wei,
622
- outcome_index=self.get_outcome_index(
623
- self.get_outcome_str_from_bool(direction)
624
- ),
628
+ outcome_index=self.get_outcome_index(outcome),
625
629
  )
626
630
  received_token_amount = received_token_amount_wei.as_outcome_token
627
631
  return received_token_amount
628
632
 
629
- def get_new_p_yes(self, bet_amount: USD, direction: bool) -> Probability:
630
- """
631
- Calculate the new p_yes based on the bet amount and direction.
632
- """
633
- if not self.has_token_pool():
634
- raise ValueError("Outcome token pool is required to calculate new p_yes.")
635
-
636
- bet_amount_in_tokens = self.get_usd_in_token(bet_amount)
637
- outcome_token_pool = check_not_none(self.outcome_token_pool)
638
-
639
- yes_outcome_pool_size = outcome_token_pool[
640
- self.get_outcome_str_from_bool(True)
641
- ].value
642
- no_outcome_pool_size = outcome_token_pool[
643
- self.get_outcome_str_from_bool(False)
644
- ].value
645
-
646
- new_yes_outcome_pool_size = yes_outcome_pool_size + (
647
- self.fees.get_after_fees(bet_amount_in_tokens).value
648
- )
649
- new_no_outcome_pool_size = no_outcome_pool_size + (
650
- self.fees.get_after_fees(bet_amount_in_tokens).value
651
- )
652
-
653
- received_token_amount = self.get_buy_token_amount(bet_amount, direction).value
654
- if direction:
655
- new_yes_outcome_pool_size -= received_token_amount
656
- else:
657
- new_no_outcome_pool_size -= received_token_amount
658
-
659
- new_p_yes = new_no_outcome_pool_size / (
660
- new_yes_outcome_pool_size + new_no_outcome_pool_size
661
- )
662
- return Probability(new_p_yes)
663
-
664
633
  @staticmethod
665
634
  def get_user_balance(user_id: str) -> float:
666
635
  return float(get_balances(Web3.to_checksum_address(user_id)).total)
@@ -688,22 +657,6 @@ def get_omen_user_url(address: ChecksumAddress) -> str:
688
657
  return f"https://gnosisscan.io/address/{address}"
689
658
 
690
659
 
691
- def pick_binary_market(
692
- sort_by: SortBy = SortBy.CLOSING_SOONEST,
693
- filter_by: FilterBy = FilterBy.OPEN,
694
- collateral_token_address_in: (
695
- tuple[ChecksumAddress, ...] | None
696
- ) = SAFE_COLLATERAL_TOKENS_ADDRESSES,
697
- ) -> OmenMarket:
698
- subgraph_handler = OmenSubgraphHandler()
699
- return subgraph_handler.get_omen_binary_markets_simple(
700
- limit=1,
701
- sort_by=sort_by,
702
- filter_by=filter_by,
703
- collateral_token_address_in=collateral_token_address_in,
704
- )[0]
705
-
706
-
707
660
  @tenacity.retry(
708
661
  stop=tenacity.stop_after_attempt(3),
709
662
  wait=tenacity.wait_fixed(1),
@@ -713,7 +666,7 @@ def omen_buy_outcome_tx(
713
666
  api_keys: APIKeys,
714
667
  amount: USD | CollateralToken,
715
668
  market: OmenAgentMarket,
716
- outcome: str,
669
+ outcome: OutcomeStr,
717
670
  auto_deposit: bool,
718
671
  web3: Web3 | None = None,
719
672
  slippage: float = 0.01,
@@ -769,7 +722,7 @@ def binary_omen_buy_outcome_tx(
769
722
  api_keys: APIKeys,
770
723
  amount: USD | CollateralToken,
771
724
  market: OmenAgentMarket,
772
- binary_outcome: bool,
725
+ outcome: OutcomeStr,
773
726
  auto_deposit: bool,
774
727
  web3: Web3 | None = None,
775
728
  ) -> str:
@@ -777,7 +730,7 @@ def binary_omen_buy_outcome_tx(
777
730
  api_keys=api_keys,
778
731
  amount=amount,
779
732
  market=market,
780
- outcome=get_bet_outcome(binary_outcome),
733
+ outcome=outcome,
781
734
  auto_deposit=auto_deposit,
782
735
  web3=web3,
783
736
  )
@@ -787,7 +740,7 @@ def omen_sell_outcome_tx(
787
740
  api_keys: APIKeys,
788
741
  amount: OutcomeToken | CollateralToken | USD,
789
742
  market: OmenAgentMarket,
790
- outcome: str,
743
+ outcome: OutcomeStr,
791
744
  auto_withdraw: bool,
792
745
  web3: Web3 | None = None,
793
746
  slippage: float = 0.01,
@@ -863,7 +816,7 @@ def binary_omen_sell_outcome_tx(
863
816
  api_keys: APIKeys,
864
817
  amount: OutcomeToken | CollateralToken | USD,
865
818
  market: OmenAgentMarket,
866
- binary_outcome: bool,
819
+ outcome: OutcomeStr,
867
820
  auto_withdraw: bool,
868
821
  web3: Web3 | None = None,
869
822
  ) -> str:
@@ -871,7 +824,7 @@ def binary_omen_sell_outcome_tx(
871
824
  api_keys=api_keys,
872
825
  amount=amount,
873
826
  market=market,
874
- outcome=get_bet_outcome(binary_outcome),
827
+ outcome=outcome,
875
828
  auto_withdraw=auto_withdraw,
876
829
  web3=web3,
877
830
  )
@@ -1354,9 +1307,8 @@ def send_keeping_token_to_eoa_xdai(
1354
1307
 
1355
1308
  def get_buy_outcome_token_amount(
1356
1309
  investment_amount: CollateralToken,
1357
- buy_direction: bool,
1358
- yes_outcome_pool_size: OutcomeToken,
1359
- no_outcome_pool_size: OutcomeToken,
1310
+ outcome_index: int,
1311
+ pool_balances: list[OutcomeToken],
1360
1312
  fees: MarketFees,
1361
1313
  ) -> OutcomeToken:
1362
1314
  """
@@ -1364,19 +1316,24 @@ def get_buy_outcome_token_amount(
1364
1316
 
1365
1317
  Taken from https://github.com/gnosis/conditional-tokens-market-makers/blob/6814c0247c745680bb13298d4f0dd7f5b574d0db/contracts/FixedProductMarketMaker.sol#L264
1366
1318
  """
1319
+ if outcome_index >= len(pool_balances):
1320
+ raise ValueError("invalid outcome index")
1321
+
1367
1322
  investment_amount_minus_fees = fees.get_after_fees(investment_amount)
1368
1323
  investment_amount_minus_fees_as_ot = OutcomeToken(
1369
1324
  investment_amount_minus_fees.value
1370
1325
  )
1371
- buy_token_pool_balance = (
1372
- yes_outcome_pool_size if buy_direction else no_outcome_pool_size
1373
- )
1374
1326
 
1375
- pool_balance = no_outcome_pool_size if buy_direction else yes_outcome_pool_size
1376
- denominator = pool_balance + investment_amount_minus_fees_as_ot
1377
- ending_outcome_balance = OutcomeToken(
1378
- buy_token_pool_balance * pool_balance / denominator
1379
- )
1327
+ buy_token_pool_balance = pool_balances[outcome_index]
1328
+ ending_outcome_balance = buy_token_pool_balance
1329
+
1330
+ # Calculate the ending balance considering all other outcomes
1331
+ for i, pool_balance in enumerate(pool_balances):
1332
+ if i != outcome_index:
1333
+ denominator = pool_balance + investment_amount_minus_fees_as_ot
1334
+ ending_outcome_balance = OutcomeToken(
1335
+ (ending_outcome_balance * pool_balance / denominator)
1336
+ )
1380
1337
 
1381
1338
  if ending_outcome_balance <= 0:
1382
1339
  raise ValueError("must have non-zero balances")
@@ -697,21 +697,18 @@ class OmenRealitioContract(ContractOnGnosisChain):
697
697
  self,
698
698
  api_keys: APIKeys,
699
699
  question_id: HexBytes,
700
- answer: str,
701
- outcomes: t.Sequence[str],
700
+ outcome_index: int,
702
701
  bond: xDaiWei,
703
702
  max_previous: xDaiWei | None = None,
704
703
  web3: Web3 | None = None,
705
704
  ) -> TxReceipt:
706
705
  # Normalise the answer to lowercase, to match Enum values as [YES, NO] against outcomes as ["Yes", "No"].
707
- answer = answer.lower()
708
- outcomes = [o.lower() for o in outcomes]
709
706
 
710
707
  return self.submitAnswer(
711
708
  api_keys=api_keys,
712
709
  question_id=question_id,
713
710
  answer=int_to_hexbytes(
714
- outcomes.index(answer)
711
+ outcome_index
715
712
  ), # Contract's method expects answer index in bytes.
716
713
  bond=bond,
717
714
  max_previous=max_previous,
@@ -10,7 +10,7 @@ from prediction_market_agent_tooling.gtypes import (
10
10
  )
11
11
  from prediction_market_agent_tooling.loggers import logger
12
12
  from prediction_market_agent_tooling.markets.data_models import Resolution
13
- from prediction_market_agent_tooling.markets.manifold.utils import (
13
+ from prediction_market_agent_tooling.markets.manifold.api import (
14
14
  find_resolution_on_manifold,
15
15
  )
16
16
  from prediction_market_agent_tooling.markets.markets import MarketType
@@ -168,28 +168,28 @@ def finalize_markets(
168
168
  f"Skipping, no resolution provided, market closed before {closed_before_days} days: {market.url=}"
169
169
  )
170
170
 
171
- elif resolution in (Resolution.YES, Resolution.NO):
172
- logger.info(f"Found resolution {resolution.value=} for {market.url=}")
173
- omen_submit_answer_market_tx(
171
+ elif resolution.invalid:
172
+ logger.warning(
173
+ f"Invalid resolution found, {resolution=}, for {market.url=}, finalizing as invalid."
174
+ )
175
+ omen_submit_invalid_answer_market_tx(
174
176
  api_keys,
175
177
  market,
176
- resolution,
177
178
  realitio_bond,
178
179
  web3=web3,
179
180
  )
180
- finalized_markets.append(market.id)
181
- logger.info(f"Finalized {market.url=}")
182
181
 
183
182
  else:
184
- logger.warning(
185
- f"Invalid resolution found, {resolution=}, for {market.url=}, finalizing as invalid."
186
- )
187
- omen_submit_invalid_answer_market_tx(
183
+ logger.info(f"Found resolution {resolution=} for {market.url=}")
184
+ omen_submit_answer_market_tx(
188
185
  api_keys,
189
186
  market,
187
+ resolution,
190
188
  realitio_bond,
191
189
  web3=web3,
192
190
  )
191
+ finalized_markets.append(market.id)
192
+ logger.info(f"Finalized {market.url=}")
193
193
 
194
194
  return finalized_markets
195
195
 
@@ -223,11 +223,11 @@ def omen_submit_answer_market_tx(
223
223
  And after the period is over, you need to resolve the market using `omen_resolve_market_tx`.
224
224
  """
225
225
  realitio_contract = OmenRealitioContract()
226
+ outcome_index = market.outcomes.index(resolution.outcome)
226
227
  realitio_contract.submit_answer(
227
228
  api_keys=api_keys,
228
229
  question_id=market.question.id,
229
- answer=resolution.value,
230
- outcomes=market.question.outcomes,
230
+ outcome_index=outcome_index,
231
231
  bond=bond.as_xdai_wei,
232
232
  web3=web3,
233
233
  )
@@ -23,7 +23,6 @@ from prediction_market_agent_tooling.markets.omen.data_models import (
23
23
  OmenMarket,
24
24
  OmenPosition,
25
25
  OmenUserPosition,
26
- OutcomeStr,
27
26
  OutcomeWei,
28
27
  RealityAnswer,
29
28
  RealityQuestion,
@@ -221,7 +220,6 @@ class OmenSubgraphHandler(BaseSubgraphHandler):
221
220
  self,
222
221
  creator: HexAddress | None,
223
222
  creator_in: t.Sequence[HexAddress] | None,
224
- outcomes: t.Sequence[OutcomeStr],
225
223
  created_after: DatetimeUTC | None,
226
224
  question_opened_before: DatetimeUTC | None,
227
225
  question_opened_after: DatetimeUTC | None,
@@ -239,12 +237,15 @@ class OmenSubgraphHandler(BaseSubgraphHandler):
239
237
  id_in: list[str] | None,
240
238
  collateral_token_address_in: tuple[ChecksumAddress, ...] | None,
241
239
  category: str | None,
240
+ include_categorical_markets: bool = False,
242
241
  ) -> dict[str, t.Any]:
243
242
  where_stms: dict[str, t.Any] = {
244
- "outcomes": outcomes,
245
243
  "title_not": None,
246
244
  "condition_": {},
247
245
  }
246
+ if not include_categorical_markets:
247
+ where_stms["outcomeSlotCount"] = 2
248
+ where_stms["outcomes"] = OMEN_BINARY_MARKET_OUTCOMES
248
249
 
249
250
  where_stms["question_"] = self.get_omen_question_filters(
250
251
  question_id=question_id,
@@ -329,12 +330,13 @@ class OmenSubgraphHandler(BaseSubgraphHandler):
329
330
 
330
331
  return sort_direction, sort_by_field
331
332
 
332
- def get_omen_binary_markets_simple(
333
+ def get_omen_markets_simple(
333
334
  self,
334
335
  limit: t.Optional[int],
335
336
  # Enumerated values for simpler usage.
336
337
  filter_by: FilterBy,
337
338
  sort_by: SortBy,
339
+ include_categorical_markets: bool = False,
338
340
  # Additional filters, these can not be modified by the enums above.
339
341
  created_after: DatetimeUTC | None = None,
340
342
  excluded_questions: set[str] | None = None, # question titles
@@ -345,7 +347,7 @@ class OmenSubgraphHandler(BaseSubgraphHandler):
345
347
  creator_in: t.Sequence[HexAddress] | None = None,
346
348
  ) -> t.List[OmenMarket]:
347
349
  """
348
- Simplified `get_omen_binary_markets` method, which allows to fetch markets based on the filter_by and sort_by values.
350
+ Simplified `get_omen_markets` method, which allows to fetch markets based on the filter_by and sort_by values.
349
351
  """
350
352
  # These values need to be set according to the filter_by value, so they can not be passed as arguments.
351
353
  resolved: bool | None = None
@@ -367,7 +369,7 @@ class OmenSubgraphHandler(BaseSubgraphHandler):
367
369
 
368
370
  sort_direction, sort_by_field = self._build_sort_params(sort_by)
369
371
 
370
- return self.get_omen_binary_markets(
372
+ all_markets = self.get_omen_markets(
371
373
  limit=limit,
372
374
  resolved=resolved,
373
375
  question_opened_after=opened_after,
@@ -379,9 +381,12 @@ class OmenSubgraphHandler(BaseSubgraphHandler):
379
381
  collateral_token_address_in=collateral_token_address_in,
380
382
  category=category,
381
383
  creator_in=creator_in,
384
+ include_categorical_markets=include_categorical_markets,
382
385
  )
383
386
 
384
- def get_omen_binary_markets(
387
+ return all_markets
388
+
389
+ def get_omen_markets(
385
390
  self,
386
391
  limit: t.Optional[int],
387
392
  creator: HexAddress | None = None,
@@ -403,19 +408,18 @@ class OmenSubgraphHandler(BaseSubgraphHandler):
403
408
  id_in: list[str] | None = None,
404
409
  sort_by_field: FieldPath | None = None,
405
410
  sort_direction: str | None = None,
406
- outcomes: t.Sequence[OutcomeStr] = OMEN_BINARY_MARKET_OUTCOMES,
407
411
  collateral_token_address_in: (
408
412
  tuple[ChecksumAddress, ...] | None
409
413
  ) = SAFE_COLLATERAL_TOKENS_ADDRESSES,
410
414
  category: str | None = None,
415
+ include_categorical_markets: bool = True,
411
416
  ) -> t.List[OmenMarket]:
412
417
  """
413
- Complete method to fetch Omen binary markets with various filters, use `get_omen_binary_markets_simple` for simplified version that uses FilterBy and SortBy enums.
418
+ Complete method to fetch Omen markets with various filters, use `get_omen_markets_simple` for simplified version that uses FilterBy and SortBy enums.
414
419
  """
415
420
  where_stms = self._build_where_statements(
416
421
  creator=creator,
417
422
  creator_in=creator_in,
418
- outcomes=outcomes,
419
423
  created_after=created_after,
420
424
  question_opened_before=question_opened_before,
421
425
  question_opened_after=question_opened_after,
@@ -433,6 +437,7 @@ class OmenSubgraphHandler(BaseSubgraphHandler):
433
437
  liquidity_bigger_than=liquidity_bigger_than,
434
438
  collateral_token_address_in=collateral_token_address_in,
435
439
  category=category,
440
+ include_categorical_markets=include_categorical_markets,
436
441
  )
437
442
 
438
443
  # These values can not be set to `None`, but they can be omitted.
@@ -451,6 +456,7 @@ class OmenSubgraphHandler(BaseSubgraphHandler):
451
456
  )
452
457
 
453
458
  fields = self._get_fields_for_markets(markets)
459
+
454
460
  omen_markets = self.do_query(fields=fields, pydantic_model=OmenMarket)
455
461
  return omen_markets
456
462
 
@@ -914,7 +920,7 @@ class OmenSubgraphHandler(BaseSubgraphHandler):
914
920
  unique_condition_ids: list[HexBytes] = list(
915
921
  set(sum([u.position.conditionIds for u in user_positions], []))
916
922
  )
917
- markets = self.get_omen_binary_markets(
923
+ markets = self.get_omen_markets(
918
924
  limit=sys.maxsize, condition_id_in=unique_condition_ids
919
925
  )
920
926
  return markets
@@ -924,7 +930,7 @@ class OmenSubgraphHandler(BaseSubgraphHandler):
924
930
  ) -> OmenMarket:
925
931
  """Markets and user positions are uniquely connected via condition_ids"""
926
932
  condition_ids = user_position.position.conditionIds
927
- markets = self.get_omen_binary_markets(limit=1, condition_id_in=condition_ids)
933
+ markets = self.get_omen_markets(limit=1, condition_id_in=condition_ids)
928
934
  if len(markets) != 1:
929
935
  raise ValueError(
930
936
  f"Incorrect number of markets fetched {len(markets)}, expected 1."
@@ -69,17 +69,21 @@ class PolymarketMarket(BaseModel):
69
69
  def resolution(self) -> Resolution | None:
70
70
  winner_tokens = [token for token in self.tokens if token.winner]
71
71
  if len(winner_tokens) == 0:
72
- return None
72
+ return Resolution(outcome=None, invalid=True)
73
73
  elif (
74
74
  len(winner_tokens) == 1
75
75
  and winner_tokens[0].outcome == POLYMARKET_TRUE_OUTCOME
76
76
  ):
77
- return Resolution.YES
77
+ return Resolution(
78
+ outcome=OutcomeStr(POLYMARKET_TRUE_OUTCOME), invalid=False
79
+ )
78
80
  elif (
79
81
  len(winner_tokens) == 1
80
82
  and winner_tokens[0].outcome == POLYMARKET_FALSE_OUTCOME
81
83
  ):
82
- return Resolution.NO
84
+ return Resolution(
85
+ outcome=OutcomeStr(POLYMARKET_FALSE_OUTCOME), invalid=False
86
+ )
83
87
  else:
84
88
  raise ValueError(
85
89
  f"Should not happen, invalid winner tokens: {winner_tokens}"