prediction-market-agent-tooling 0.62.0.dev493__py3-none-any.whl → 0.63.0.dev498__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 (20) hide show
  1. prediction_market_agent_tooling/deploy/agent.py +21 -5
  2. prediction_market_agent_tooling/deploy/betting_strategy.py +20 -5
  3. prediction_market_agent_tooling/markets/agent_market.py +3 -1
  4. prediction_market_agent_tooling/markets/blockchain_utils.py +6 -2
  5. prediction_market_agent_tooling/markets/omen/omen.py +1 -0
  6. prediction_market_agent_tooling/markets/seer/data_models.py +5 -63
  7. prediction_market_agent_tooling/markets/seer/price_manager.py +119 -0
  8. prediction_market_agent_tooling/markets/seer/seer.py +101 -44
  9. prediction_market_agent_tooling/markets/seer/seer_contracts.py +1 -1
  10. prediction_market_agent_tooling/markets/seer/seer_subgraph_handler.py +47 -18
  11. prediction_market_agent_tooling/markets/seer/subgraph_data_models.py +57 -0
  12. prediction_market_agent_tooling/tools/contract.py +11 -0
  13. prediction_market_agent_tooling/tools/cow/cow_order.py +47 -52
  14. prediction_market_agent_tooling/tools/tokens/auto_deposit.py +1 -0
  15. {prediction_market_agent_tooling-0.62.0.dev493.dist-info → prediction_market_agent_tooling-0.63.0.dev498.dist-info}/METADATA +1 -1
  16. {prediction_market_agent_tooling-0.62.0.dev493.dist-info → prediction_market_agent_tooling-0.63.0.dev498.dist-info}/RECORD +19 -18
  17. prediction_market_agent_tooling/tools/cow/cow_manager.py +0 -112
  18. {prediction_market_agent_tooling-0.62.0.dev493.dist-info → prediction_market_agent_tooling-0.63.0.dev498.dist-info}/LICENSE +0 -0
  19. {prediction_market_agent_tooling-0.62.0.dev493.dist-info → prediction_market_agent_tooling-0.63.0.dev498.dist-info}/WHEEL +0 -0
  20. {prediction_market_agent_tooling-0.62.0.dev493.dist-info → prediction_market_agent_tooling-0.63.0.dev498.dist-info}/entry_points.txt +0 -0
@@ -65,7 +65,11 @@ from prediction_market_agent_tooling.tools.langfuse_ import langfuse_context, ob
65
65
  from prediction_market_agent_tooling.tools.tokens.main_token import (
66
66
  MINIMUM_NATIVE_TOKEN_IN_EOA_FOR_FEES,
67
67
  )
68
- from prediction_market_agent_tooling.tools.utils import DatetimeUTC, utcnow
68
+ from prediction_market_agent_tooling.tools.utils import (
69
+ DatetimeUTC,
70
+ check_not_none,
71
+ utcnow,
72
+ )
69
73
 
70
74
  MAX_AVAILABLE_MARKETS = 1000
71
75
 
@@ -615,9 +619,9 @@ class DeployableTraderAgent(DeployablePredictionAgent):
615
619
  return None
616
620
 
617
621
  api_keys = APIKeys()
618
- existing_position = market.get_position(
619
- user_id=market.get_user_id(api_keys=api_keys)
620
- )
622
+ user_id = market.get_user_id(api_keys=api_keys)
623
+
624
+ existing_position = market.get_position(user_id=user_id)
621
625
  trades = self.build_trades(
622
626
  market=market,
623
627
  answer=processed_market.answer,
@@ -643,8 +647,20 @@ class DeployableTraderAgent(DeployablePredictionAgent):
643
647
  outcome=trade.outcome, amount=trade.amount
644
648
  )
645
649
  case TradeType.SELL:
650
+ # Get actual value of the position we are going to sell, and if it's less than we wanted to sell, simply sell all of it.
651
+ current_position_value = check_not_none(
652
+ market.get_position(user_id),
653
+ "Should exists if we are going to sell outcomes.",
654
+ ).amounts_current[
655
+ market.get_outcome_str_from_bool(trade.outcome)
656
+ ]
657
+ if current_position_value < trade.amount:
658
+ logger.warning(
659
+ f"Current value of position {trade.outcome=}, {current_position_value=} is less than the desired selling amount {trade.amount=}. Selling all."
660
+ )
646
661
  id = market.sell_tokens(
647
- outcome=trade.outcome, amount=trade.amount
662
+ outcome=trade.outcome,
663
+ amount=min(trade.amount, current_position_value),
648
664
  )
649
665
  case _:
650
666
  raise ValueError(f"Unexpected trade type {trade.trade_type}.")
@@ -47,7 +47,8 @@ class BettingStrategy(ABC):
47
47
  @staticmethod
48
48
  def assert_buy_trade_wont_be_guaranteed_loss(
49
49
  market: AgentMarket, trades: list[Trade]
50
- ) -> None:
50
+ ) -> list[Trade]:
51
+ clean_trades = []
51
52
  for trade in trades:
52
53
  if trade.trade_type == TradeType.BUY:
53
54
  outcome_tokens_to_get = market.get_buy_token_amount(
@@ -56,14 +57,28 @@ class BettingStrategy(ABC):
56
57
  outcome_tokens_to_get_in_usd = market.get_token_in_usd(
57
58
  outcome_tokens_to_get.as_token
58
59
  )
60
+
61
+ if not outcome_tokens_to_get:
62
+ logger.info(
63
+ f"Could not determine buy_token_amount for trade {trade}. Skipping trade."
64
+ )
65
+ continue
66
+
59
67
  if outcome_tokens_to_get_in_usd <= trade.amount:
60
68
  raise GuaranteedLossError(
61
- f"Trade {trade=} would result in guaranteed loss by getting only {outcome_tokens_to_get=}."
69
+ f"Trade {trade=} would result in guaranteed loss by getting only {outcome_tokens_to_get=}. Halting execution."
62
70
  )
63
71
 
72
+ clean_trades.append(trade)
73
+
74
+ return clean_trades
75
+
64
76
  @staticmethod
65
- def check_trades(market: AgentMarket, trades: list[Trade]) -> None:
66
- BettingStrategy.assert_buy_trade_wont_be_guaranteed_loss(market, trades)
77
+ def filter_trades(market: AgentMarket, trades: list[Trade]) -> list[Trade]:
78
+ trades = BettingStrategy.assert_buy_trade_wont_be_guaranteed_loss(
79
+ market, trades
80
+ )
81
+ return trades
67
82
 
68
83
  def _build_rebalance_trades_from_positions(
69
84
  self,
@@ -109,7 +124,7 @@ class BettingStrategy(ABC):
109
124
  trades.sort(key=lambda t: t.trade_type == TradeType.SELL)
110
125
 
111
126
  # Run some sanity checks to not place unreasonable bets.
112
- BettingStrategy.check_trades(market, trades)
127
+ trades = BettingStrategy.filter_trades(market, trades)
113
128
 
114
129
  return trades
115
130
 
@@ -4,6 +4,7 @@ from enum import Enum
4
4
  from eth_typing import ChecksumAddress
5
5
  from pydantic import BaseModel, field_validator, model_validator
6
6
  from pydantic_core.core_schema import FieldValidationInfo
7
+ from web3 import Web3
7
8
 
8
9
  from prediction_market_agent_tooling.config import APIKeys
9
10
  from prediction_market_agent_tooling.gtypes import (
@@ -235,7 +236,7 @@ class AgentMarket(BaseModel):
235
236
 
236
237
  def get_buy_token_amount(
237
238
  self, bet_amount: USD | CollateralToken, direction: bool
238
- ) -> OutcomeToken:
239
+ ) -> OutcomeToken | None:
239
240
  raise NotImplementedError("Subclasses must implement this method")
240
241
 
241
242
  def sell_tokens(self, outcome: bool, amount: USD | OutcomeToken) -> str:
@@ -293,6 +294,7 @@ class AgentMarket(BaseModel):
293
294
  traded_market: ProcessedTradedMarket | None,
294
295
  keys: APIKeys,
295
296
  agent_name: str,
297
+ web3: Web3 | None = None,
296
298
  ) -> None:
297
299
  """
298
300
  If market allows to upload trades somewhere, implement it in this method.
@@ -22,10 +22,11 @@ def store_trades(
22
22
  traded_market: ProcessedTradedMarket | None,
23
23
  keys: APIKeys,
24
24
  agent_name: str,
25
+ web3: Web3 | None = None,
25
26
  ) -> None:
26
27
  if traded_market is None:
27
28
  logger.warning(f"No prediction for market {market_id}, not storing anything.")
28
- return
29
+ return None
29
30
 
30
31
  reasoning = traded_market.answer.reasoning if traded_market.answer.reasoning else ""
31
32
 
@@ -37,8 +38,10 @@ def store_trades(
37
38
  )
38
39
  ipfs_hash_decoded = ipfscidv0_to_byte32(ipfs_hash)
39
40
 
41
+ # tx_hashes must be list of bytes32 (see Solidity contract).
42
+ # For regular tx hashes that's fine, but for other types of IDs we take the first 32 bytes (orderDigest).
40
43
  tx_hashes = [
41
- HexBytes(HexStr(i.id)) for i in traded_market.trades if i.id is not None
44
+ HexBytes(HexStr(i.id[:32])) for i in traded_market.trades if i.id is not None
42
45
  ]
43
46
  prediction = ContractPrediction(
44
47
  publisher=keys.bet_from_address,
@@ -50,6 +53,7 @@ def store_trades(
50
53
  api_keys=keys,
51
54
  market_address=Web3.to_checksum_address(market_id),
52
55
  prediction=prediction,
56
+ web3=web3,
53
57
  )
54
58
  logger.info(
55
59
  f"Added prediction to market {market_id}. - receipt {tx_receipt['transactionHash'].hex()}."
@@ -424,6 +424,7 @@ class OmenAgentMarket(AgentMarket):
424
424
  traded_market: ProcessedTradedMarket | None,
425
425
  keys: APIKeys,
426
426
  agent_name: str,
427
+ web3: Web3 | None = None,
427
428
  ) -> None:
428
429
  return store_trades(
429
430
  market_id=self.id,
@@ -5,9 +5,9 @@ from urllib.parse import urljoin
5
5
 
6
6
  from pydantic import BaseModel, ConfigDict, Field
7
7
  from web3 import Web3
8
- from web3.constants import ADDRESS_ZERO
9
8
 
10
9
  from prediction_market_agent_tooling.config import RPCConfig
10
+ from prediction_market_agent_tooling.gtypes import ChecksumAddress, HexAddress, HexBytes
11
11
  from prediction_market_agent_tooling.gtypes import (
12
12
  ChecksumAddress,
13
13
  CollateralToken,
@@ -19,7 +19,9 @@ from prediction_market_agent_tooling.gtypes import (
19
19
  )
20
20
  from prediction_market_agent_tooling.loggers import logger
21
21
  from prediction_market_agent_tooling.markets.data_models import Resolution
22
- from prediction_market_agent_tooling.tools.cow.cow_manager import CowManager
22
+ from prediction_market_agent_tooling.markets.seer.subgraph_data_models import (
23
+ SeerParentMarket,
24
+ )
23
25
  from prediction_market_agent_tooling.tools.datetime_utc import DatetimeUTC
24
26
 
25
27
 
@@ -84,10 +86,6 @@ class SeerOutcomeEnum(str, Enum):
84
86
  raise ValueError(f"Unknown outcome: {self}")
85
87
 
86
88
 
87
- class SeerParentMarket(BaseModel):
88
- id: HexBytes
89
-
90
-
91
89
  SEER_BASE_URL = "https://app.seer.pm"
92
90
 
93
91
 
@@ -110,6 +108,7 @@ class SeerMarket(BaseModel):
110
108
  has_answers: bool | None = Field(alias="hasAnswers")
111
109
  payout_reported: bool = Field(alias="payoutReported")
112
110
  payout_numerators: list[int] = Field(alias="payoutNumerators")
111
+ outcomes_supply: int = Field(alias="outcomesSupply")
113
112
 
114
113
  @property
115
114
  def has_valid_answer(self) -> bool:
@@ -186,64 +185,7 @@ class SeerMarket(BaseModel):
186
185
  def created_time(self) -> DatetimeUTC:
187
186
  return DatetimeUTC.to_datetime_utc(self.block_timestamp)
188
187
 
189
- @property
190
- def current_p_yes(self) -> Probability:
191
- price_data = {}
192
- for idx in range(len(self.outcomes)):
193
- wrapped_token = self.wrapped_tokens[idx]
194
- price = self._get_price_for_token(
195
- token=Web3.to_checksum_address(wrapped_token)
196
- )
197
- price_data[idx] = price
198
-
199
- if sum(price_data.values()) == 0:
200
- logger.warning(
201
- f"Could not get p_yes for market {self.id.hex()}, all price quotes are 0."
202
- )
203
- return Probability(0)
204
-
205
- yes_idx = self.outcome_as_enums[SeerOutcomeEnum.YES]
206
- price_yes = price_data[yes_idx] / sum(price_data.values())
207
- return Probability(price_yes)
208
-
209
- def _get_price_for_token(self, token: ChecksumAddress) -> float:
210
- collateral_exchange_amount = CollateralToken(1).as_wei
211
- try:
212
- quote = CowManager().get_quote(
213
- collateral_token=self.collateral_token_contract_address_checksummed,
214
- buy_token=token,
215
- sell_amount=collateral_exchange_amount,
216
- )
217
- except Exception as e:
218
- logger.warning(f"Could not get quote for {token=}, returning price 0. {e=}")
219
- return 0
220
-
221
- return collateral_exchange_amount.value / float(quote.quote.buyAmount.root)
222
-
223
188
  @property
224
189
  def url(self) -> str:
225
190
  chain_id = RPCConfig().chain_id
226
191
  return urljoin(SEER_BASE_URL, f"markets/{chain_id}/{self.id.hex()}")
227
-
228
-
229
- class SeerToken(BaseModel):
230
- id: HexBytes
231
- name: str
232
- symbol: str
233
-
234
-
235
- class SeerPool(BaseModel):
236
- model_config = ConfigDict(populate_by_name=True)
237
- id: HexBytes
238
- liquidity: int
239
- token0: SeerToken
240
- token1: SeerToken
241
-
242
-
243
- class NewMarketEvent(BaseModel):
244
- market: HexAddress
245
- marketName: str
246
- parentMarket: HexAddress
247
- conditionId: HexBytes
248
- questionId: HexBytes
249
- questionsIds: list[HexBytes]
@@ -0,0 +1,119 @@
1
+ from functools import lru_cache
2
+
3
+ from web3 import Web3
4
+
5
+ from prediction_market_agent_tooling.gtypes import (
6
+ ChecksumAddress,
7
+ Probability,
8
+ xdai_type,
9
+ Wei,
10
+ )
11
+ from prediction_market_agent_tooling.loggers import logger
12
+ from prediction_market_agent_tooling.markets.seer.data_models import (
13
+ SeerMarket,
14
+ SeerOutcomeEnum,
15
+ )
16
+ from prediction_market_agent_tooling.markets.seer.seer_subgraph_handler import (
17
+ SeerSubgraphHandler,
18
+ )
19
+ from prediction_market_agent_tooling.markets.seer.subgraph_data_models import SeerPool
20
+ from prediction_market_agent_tooling.tools.cow.cow_order import get_quote
21
+ from prediction_market_agent_tooling.tools.hexbytes_custom import HexBytes
22
+ from prediction_market_agent_tooling.tools.web3_utils import xdai_to_wei
23
+
24
+
25
+ class PriceManager:
26
+ def __init__(self, seer_market: SeerMarket, seer_subgraph: SeerSubgraphHandler):
27
+ self.seer_market = seer_market
28
+ self.seer_subgraph = seer_subgraph
29
+
30
+ @staticmethod
31
+ def build(market_id: HexBytes) -> "PriceManager":
32
+ s = SeerSubgraphHandler()
33
+ market = s.get_market_by_id(market_id=market_id)
34
+ return PriceManager(seer_market=market, seer_subgraph=s)
35
+
36
+ def _log_track_price_normalization_diff(
37
+ self, old_price: float, normalized_price: float, max_price_diff: float = 0.05
38
+ ) -> None:
39
+ price_diff_pct = abs(old_price - normalized_price) / old_price
40
+ if price_diff_pct > max_price_diff:
41
+ logger.info(
42
+ f"{price_diff_pct=} larger than {max_price_diff=} for seer market {self.seer_market.id.hex()} "
43
+ )
44
+
45
+ def current_p_yes(self) -> Probability | None:
46
+ # Inspired by https://github.com/seer-pm/demo/blob/ca682153a6b4d4dd3dcc4ad8bdcbe32202fc8fe7/web/src/hooks/useMarketOdds.ts#L15
47
+ price_data = {}
48
+ for idx, wrapped_token in enumerate(self.seer_market.wrapped_tokens):
49
+ price = self.get_price_for_token(
50
+ token=Web3.to_checksum_address(wrapped_token),
51
+ )
52
+
53
+ price_data[idx] = price
54
+
55
+ price_yes = price_data[self.seer_market.outcome_as_enums[SeerOutcomeEnum.YES]]
56
+ price_no = price_data[self.seer_market.outcome_as_enums[SeerOutcomeEnum.NO]]
57
+
58
+ # We only return a probability if we have both price_yes and price_no, since we could place bets
59
+ # in both sides hence we need current probabilities for both outcomes.
60
+ if price_yes and price_no:
61
+ # If other outcome`s price is None, we set it to 0.
62
+ total_price = sum(
63
+ price if price is not None else 0.0 for price in price_data.values()
64
+ )
65
+ normalized_price_yes = price_yes / total_price
66
+ self._log_track_price_normalization_diff(
67
+ old_price=price_yes, normalized_price=normalized_price_yes
68
+ )
69
+ return Probability(normalized_price_yes)
70
+ else:
71
+ return None
72
+
73
+ @lru_cache(typed=True)
74
+ def get_price_for_token(
75
+ self,
76
+ token: ChecksumAddress,
77
+ collateral_exchange_amount: Wei = xdai_to_wei(xdai_type(1)),
78
+ ) -> float | None:
79
+ try:
80
+ quote = get_quote(
81
+ amount_wei=collateral_exchange_amount,
82
+ sell_token=self.seer_market.collateral_token_contract_address_checksummed,
83
+ buy_token=token,
84
+ )
85
+
86
+ except Exception as e:
87
+ logger.warning(
88
+ f"Could not get quote for {token=} from Cow, exception {e=}. Falling back to pools. "
89
+ )
90
+ return self.get_token_price_from_pools(token=token)
91
+
92
+ return collateral_exchange_amount / float(quote.quote.buyAmount.root)
93
+
94
+ @staticmethod
95
+ def _pool_token0_matches_token(token: ChecksumAddress, pool: SeerPool) -> bool:
96
+ return pool.token0.id.hex().lower() == token.lower()
97
+
98
+ def get_token_price_from_pools(
99
+ self,
100
+ token: ChecksumAddress,
101
+ ) -> float | None:
102
+ pool = SeerSubgraphHandler().get_pool_by_token(
103
+ token_address=token,
104
+ collateral_address=self.seer_market.collateral_token_contract_address_checksummed,
105
+ )
106
+
107
+ if not pool:
108
+ logger.warning(f"Could not find a pool for {token=}")
109
+ return None
110
+
111
+ # The mapping below is odd but surprisingly the Algebra subgraph delivers the token1Price
112
+ # for the token0 and the token0Price for the token1 pool.
113
+ # For example, in a outcomeYES (token0)/sDAI pool (token1), token1Price is the price of outcomeYES in units of sDAI.
114
+ price_in_collateral_units = (
115
+ pool.token1Price
116
+ if self._pool_token0_matches_token(token=token, pool=pool)
117
+ else pool.token0Price
118
+ )
119
+ return price_in_collateral_units
@@ -1,5 +1,7 @@
1
1
  import typing as t
2
2
 
3
+ import tenacity
4
+ from eth_pydantic_types import HexStr
3
5
  from eth_typing import ChecksumAddress
4
6
  from web3 import Web3
5
7
  from web3.types import TxReceipt
@@ -11,6 +13,7 @@ from prediction_market_agent_tooling.gtypes import (
11
13
  HexAddress,
12
14
  HexBytes,
13
15
  OutcomeStr,
16
+ Wei,
14
17
  OutcomeToken,
15
18
  OutcomeWei,
16
19
  xDai,
@@ -20,33 +23,33 @@ from prediction_market_agent_tooling.markets.agent_market import (
20
23
  AgentMarket,
21
24
  FilterBy,
22
25
  ProcessedMarket,
23
- ProcessedTradedMarket,
24
26
  SortBy,
27
+ ProcessedTradedMarket,
25
28
  )
26
- from prediction_market_agent_tooling.markets.blockchain_utils import store_trades
27
29
  from prediction_market_agent_tooling.markets.data_models import ExistingPosition
28
30
  from prediction_market_agent_tooling.markets.market_fees import MarketFees
29
31
  from prediction_market_agent_tooling.markets.omen.omen import OmenAgentMarket
30
32
  from prediction_market_agent_tooling.markets.seer.data_models import (
31
- NewMarketEvent,
32
33
  SeerMarket,
33
34
  SeerOutcomeEnum,
34
35
  )
36
+ from prediction_market_agent_tooling.markets.seer.price_manager import PriceManager
35
37
  from prediction_market_agent_tooling.markets.seer.seer_contracts import (
36
38
  SeerMarketFactory,
37
39
  )
38
40
  from prediction_market_agent_tooling.markets.seer.seer_subgraph_handler import (
39
41
  SeerSubgraphHandler,
40
42
  )
43
+ from prediction_market_agent_tooling.markets.seer.subgraph_data_models import (
44
+ NewMarketEvent,
45
+ )
46
+ from prediction_market_agent_tooling.tools.balances import get_balances
41
47
  from prediction_market_agent_tooling.tools.contract import (
42
48
  ContractERC20OnGnosisChain,
43
49
  init_collateral_token_contract,
44
50
  to_gnosis_chain_contract,
45
51
  )
46
- from prediction_market_agent_tooling.tools.cow.cow_manager import (
47
- CowManager,
48
- NoLiquidityAvailableOnCowException,
49
- )
52
+ from prediction_market_agent_tooling.tools.cow.cow_order import swap_tokens_waiting
50
53
  from prediction_market_agent_tooling.tools.datetime_utc import DatetimeUTC
51
54
  from prediction_market_agent_tooling.tools.tokens.auto_deposit import (
52
55
  auto_deposit_collateral_token,
@@ -87,19 +90,16 @@ class SeerAgentMarket(AgentMarket):
87
90
  agent_name: str,
88
91
  ) -> None:
89
92
  """On Seer, we have to store predictions along with trades, see `store_trades`."""
93
+ pass
90
94
 
91
95
  def store_trades(
92
96
  self,
93
97
  traded_market: ProcessedTradedMarket | None,
94
98
  keys: APIKeys,
95
99
  agent_name: str,
100
+ web3: Web3 | None = None,
96
101
  ) -> None:
97
- return store_trades(
98
- market_id=self.id,
99
- traded_market=traded_market,
100
- keys=keys,
101
- agent_name=agent_name,
102
- )
102
+ pass
103
103
 
104
104
  def get_token_in_usd(self, x: CollateralToken) -> USD:
105
105
  return get_token_in_usd(x, self.collateral_token_contract_address_checksummed)
@@ -108,20 +108,24 @@ class SeerAgentMarket(AgentMarket):
108
108
  return get_usd_in_token(x, self.collateral_token_contract_address_checksummed)
109
109
 
110
110
  def get_buy_token_amount(
111
- self, bet_amount: USD | CollateralToken, direction: bool
112
- ) -> OutcomeToken:
111
+ self, bet_amount: BetAmount, direction: bool
112
+ ) -> OutcomeToken | None:
113
113
  """Returns number of outcome tokens returned for a given bet expressed in collateral units."""
114
114
 
115
115
  outcome_token = self.get_wrapped_token_for_outcome(direction)
116
116
  bet_amount_in_tokens = self.get_in_token(bet_amount)
117
117
  bet_amount_in_wei = bet_amount_in_tokens.as_wei
118
118
 
119
- quote = CowManager().get_quote(
120
- buy_token=outcome_token,
121
- sell_amount=bet_amount_in_wei,
122
- collateral_token=self.collateral_token_contract_address_checksummed,
119
+ p = PriceManager.build(market_id=HexBytes(HexStr(self.id)))
120
+ price_in_collateral_units = p.get_price_for_token(
121
+ token=outcome_token, collateral_exchange_amount=bet_amount_in_wei
123
122
  )
124
- sell_amount = OutcomeWei(quote.quote.buyAmount.root).as_outcome_token
123
+ if not price_in_collateral_units:
124
+ logger.info(f"Could not get price for token {outcome_token}")
125
+ return None
126
+
127
+ buy_amount = bet_amount_in_wei / price_in_collateral_units
128
+ sell_amount = OutcomeWei(buy_amount).as_outcome_token
125
129
  return sell_amount
126
130
 
127
131
  def get_outcome_str_from_bool(self, outcome: bool) -> OutcomeStr:
@@ -185,7 +189,17 @@ class SeerAgentMarket(AgentMarket):
185
189
  return OmenAgentMarket.verify_operational_balance(api_keys=api_keys)
186
190
 
187
191
  @staticmethod
188
- def from_data_model(model: SeerMarket) -> "SeerAgentMarket":
192
+ def from_data_model_with_subgraph(
193
+ model: SeerMarket, seer_subgraph: SeerSubgraphHandler
194
+ ) -> "SeerAgentMarket" | None:
195
+ p = PriceManager(seer_market=model, seer_subgraph=seer_subgraph)
196
+ current_p_yes = p.current_p_yes()
197
+ if not current_p_yes:
198
+ logger.info(
199
+ f"p_yes for market {model.id.hex()} could not be calculated. Skipping."
200
+ )
201
+ return None
202
+
189
203
  return SeerAgentMarket(
190
204
  id=model.id.hex(),
191
205
  question=model.title,
@@ -201,7 +215,7 @@ class SeerAgentMarket(AgentMarket):
201
215
  outcome_token_pool=None,
202
216
  resolution=model.get_resolution_enum(),
203
217
  volume=None,
204
- current_p_yes=model.current_p_yes,
218
+ current_p_yes=current_p_yes,
205
219
  seer_outcomes=model.outcome_as_enums,
206
220
  )
207
221
 
@@ -213,31 +227,31 @@ class SeerAgentMarket(AgentMarket):
213
227
  created_after: t.Optional[DatetimeUTC] = None,
214
228
  excluded_questions: set[str] | None = None,
215
229
  ) -> t.Sequence["SeerAgentMarket"]:
230
+ seer_subgraph = SeerSubgraphHandler()
231
+ markets = seer_subgraph.get_binary_markets(
232
+ limit=limit, sort_by=sort_by, filter_by=filter_by
233
+ )
234
+
235
+ # We exclude the None values below because `from_data_model_with_subgraph` can return None, which
236
+ # represents an invalid market.
216
237
  return [
217
- SeerAgentMarket.from_data_model(m)
218
- for m in SeerSubgraphHandler().get_binary_markets(
219
- limit=limit,
220
- sort_by=sort_by,
221
- filter_by=filter_by,
238
+ market
239
+ for m in markets
240
+ if (
241
+ market := SeerAgentMarket.from_data_model_with_subgraph(
242
+ model=m, seer_subgraph=seer_subgraph
243
+ )
222
244
  )
245
+ is not None
223
246
  ]
224
247
 
225
248
  def has_liquidity_for_outcome(self, outcome: bool) -> bool:
226
249
  outcome_token = self.get_wrapped_token_for_outcome(outcome)
227
- try:
228
- CowManager().get_quote(
229
- collateral_token=self.collateral_token_contract_address_checksummed,
230
- buy_token=outcome_token,
231
- sell_amount=CollateralToken(
232
- 1
233
- ).as_wei, # we take 1 as a baseline value for common trades the agents take.
234
- )
235
- return True
236
- except NoLiquidityAvailableOnCowException:
237
- logger.info(
238
- f"Could not get a quote for {outcome_token=} {outcome=}, returning no liquidity"
239
- )
240
- return False
250
+ pool = SeerSubgraphHandler().get_pool_by_token(
251
+ token_address=outcome_token,
252
+ collateral_address=self.collateral_token_contract_address_checksummed,
253
+ )
254
+ return pool is not None and pool.liquidity > 0
241
255
 
242
256
  def has_liquidity(self) -> bool:
243
257
  # We conservatively define a market as having liquidity if it has liquidity for the `True` outcome token AND the `False` outcome token.
@@ -281,14 +295,57 @@ class SeerAgentMarket(AgentMarket):
281
295
  )
282
296
 
283
297
  outcome_token = self.get_wrapped_token_for_outcome(outcome)
284
- # Sell using token address
285
- order_metadata = CowManager().swap(
286
- amount=amount_in_token,
298
+ amount_to_trade = xdai_type(amount.amount)
299
+
300
+ # Sell sDAI using token address
301
+ order_metadata = swap_tokens_waiting(
302
+ amount_wei=xdai_to_wei(amount_to_trade),
287
303
  sell_token=collateral_contract.address,
288
304
  buy_token=outcome_token,
289
305
  api_keys=api_keys,
290
306
  web3=web3,
291
307
  )
308
+ logger.debug(
309
+ f"Purchased {outcome_token} in exchange for {collateral_contract.address}. Order details {order_metadata}"
310
+ )
311
+
312
+ return order_metadata.uid.root
313
+
314
+ @tenacity.retry(
315
+ stop=tenacity.stop_after_attempt(3),
316
+ wait=tenacity.wait_fixed(1),
317
+ after=lambda x: logger.debug(
318
+ f"seer_sell_outcome_tx failed, {x.attempt_number=}."
319
+ ),
320
+ )
321
+ def sell_tokens(
322
+ self,
323
+ outcome: bool,
324
+ amount: TokenAmount,
325
+ auto_withdraw: bool = True,
326
+ api_keys: APIKeys | None = None,
327
+ web3: Web3 | None = None,
328
+ ) -> str:
329
+ """
330
+ Sells the given number of shares for the given outcome in the given market.
331
+ """
332
+ outcome_token = self.get_wrapped_token_for_outcome(outcome)
333
+ api_keys = api_keys if api_keys is not None else APIKeys()
334
+ amount_to_trade = xdai_type(amount.amount)
335
+
336
+ order_metadata = swap_tokens_waiting(
337
+ amount_wei=xdai_to_wei(amount_to_trade),
338
+ sell_token=outcome_token,
339
+ buy_token=Web3.to_checksum_address(
340
+ self.collateral_token_contract_address_checksummed
341
+ ),
342
+ api_keys=api_keys,
343
+ web3=web3,
344
+ )
345
+
346
+ logger.debug(
347
+ f"Sold {outcome_token} in exchange for {self.collateral_token_contract_address_checksummed}. Order details {order_metadata}"
348
+ )
292
349
 
293
350
  return order_metadata.uid.root
294
351
 
@@ -11,7 +11,7 @@ from prediction_market_agent_tooling.gtypes import (
11
11
  OutcomeStr,
12
12
  xDai,
13
13
  )
14
- from prediction_market_agent_tooling.markets.seer.data_models import (
14
+ from prediction_market_agent_tooling.markets.seer.subgraph_data_models import (
15
15
  CreateCategoricalMarketsParams,
16
16
  )
17
17
  from prediction_market_agent_tooling.tools.contract import (
@@ -5,14 +5,14 @@ from typing import Any
5
5
  from subgrounds import FieldPath
6
6
  from web3.constants import ADDRESS_ZERO
7
7
 
8
+ from prediction_market_agent_tooling.gtypes import ChecksumAddress
9
+ from prediction_market_agent_tooling.loggers import logger
8
10
  from prediction_market_agent_tooling.markets.agent_market import FilterBy, SortBy
9
11
  from prediction_market_agent_tooling.markets.base_subgraph_handler import (
10
12
  BaseSubgraphHandler,
11
13
  )
12
- from prediction_market_agent_tooling.markets.seer.data_models import (
13
- SeerMarket,
14
- SeerPool,
15
- )
14
+ from prediction_market_agent_tooling.markets.seer.data_models import SeerMarket
15
+ from prediction_market_agent_tooling.markets.seer.subgraph_data_models import SeerPool
16
16
  from prediction_market_agent_tooling.tools.hexbytes_custom import HexBytes
17
17
  from prediction_market_agent_tooling.tools.utils import to_int_timestamp, utcnow
18
18
  from prediction_market_agent_tooling.tools.web3_utils import unwrap_generic_value
@@ -50,6 +50,7 @@ class SeerSubgraphHandler(BaseSubgraphHandler):
50
50
  markets_field.creator,
51
51
  markets_field.conditionId,
52
52
  markets_field.marketName,
53
+ markets_field.outcomesSupply,
53
54
  markets_field.parentOutcome,
54
55
  markets_field.outcomes,
55
56
  markets_field.payoutReported,
@@ -121,12 +122,16 @@ class SeerSubgraphHandler(BaseSubgraphHandler):
121
122
  match sort_by:
122
123
  case SortBy.NEWEST:
123
124
  sort_direction = "desc"
124
- sort_by_field = self.seer_subgraph.Market.block_timestamp
125
+ sort_by_field = self.seer_subgraph.Market.blockTimestamp
125
126
  case SortBy.CLOSING_SOONEST:
126
127
  sort_direction = "asc"
127
- sort_by_field = self.seer_subgraph.Market.opening_ts
128
- # ToDo - Implement liquidity conditions by looking up Swapr subgraph.
129
- case SortBy.NONE | SortBy.HIGHEST_LIQUIDITY | SortBy.LOWEST_LIQUIDITY:
128
+ sort_by_field = self.seer_subgraph.Market.openingTs
129
+ case SortBy.HIGHEST_LIQUIDITY | SortBy.LOWEST_LIQUIDITY:
130
+ sort_direction = (
131
+ "desc" if sort_by == SortBy.HIGHEST_LIQUIDITY else "asc"
132
+ )
133
+ sort_by_field = self.seer_subgraph.Market.outcomesSupply
134
+ case SortBy.NONE:
130
135
  sort_direction = None
131
136
  sort_by_field = None
132
137
  case _:
@@ -201,6 +206,9 @@ class SeerSubgraphHandler(BaseSubgraphHandler):
201
206
  fields = [
202
207
  pools_field.id,
203
208
  pools_field.liquidity,
209
+ pools_field.sqrtPrice,
210
+ pools_field.token0Price,
211
+ pools_field.token1Price,
204
212
  pools_field.token0.id,
205
213
  pools_field.token0.name,
206
214
  pools_field.token0.symbol,
@@ -210,19 +218,40 @@ class SeerSubgraphHandler(BaseSubgraphHandler):
210
218
  ]
211
219
  return fields
212
220
 
213
- def get_swapr_pools_for_market(self, market: SeerMarket) -> list[SeerPool]:
221
+ def get_pool_by_token(
222
+ self, token_address: ChecksumAddress, collateral_address: ChecksumAddress
223
+ ) -> SeerPool | None:
214
224
  # We iterate through the wrapped tokens and put them in a where clause so that we hit the subgraph endpoint just once.
215
225
  wheres = []
216
- for wrapped_token in market.wrapped_tokens:
217
- wheres.extend(
218
- [
219
- {"token0": wrapped_token.lower()},
220
- {"token1": wrapped_token.lower()},
221
- ]
222
- )
226
+ wheres.extend(
227
+ [
228
+ {
229
+ "token0": token_address.lower(),
230
+ "token1": collateral_address.lower(),
231
+ },
232
+ {
233
+ "token0": collateral_address.lower(),
234
+ "token1": token_address.lower(),
235
+ },
236
+ ]
237
+ )
238
+
239
+ optional_params = {}
240
+ optional_params["orderBy"] = self.swapr_algebra_subgraph.Pool.liquidity
241
+ optional_params["orderDirection"] = "desc"
242
+
223
243
  pools_field = self.swapr_algebra_subgraph.Query.pools(
224
- where=unwrap_generic_value({"or": wheres})
244
+ where=unwrap_generic_value({"or": wheres}, **optional_params
245
+ )
225
246
  )
226
247
  fields = self._get_fields_for_pools(pools_field)
227
248
  pools = self.do_query(fields=fields, pydantic_model=SeerPool)
228
- return pools
249
+ # We assume there is only one pool for outcomeToken/sDAI.
250
+ if len(pools) > 1:
251
+ logger.info(
252
+ f"Multiple pools found for token {token_address}, selecting the first."
253
+ )
254
+ if pools:
255
+ # We select the first one
256
+ return pools[0]
257
+ return None
@@ -0,0 +1,57 @@
1
+ from pydantic import BaseModel, ConfigDict, Field
2
+ from web3.constants import ADDRESS_ZERO
3
+
4
+ from prediction_market_agent_tooling.gtypes import HexAddress, HexBytes, Wei
5
+
6
+
7
+ class SeerToken(BaseModel):
8
+ id: HexBytes
9
+ name: str
10
+ symbol: str
11
+
12
+
13
+ class SeerPool(BaseModel):
14
+ model_config = ConfigDict(populate_by_name=True)
15
+ id: HexBytes
16
+ liquidity: int
17
+ token0: SeerToken
18
+ token1: SeerToken
19
+ token0Price: float
20
+ token1Price: float
21
+ sqrtPrice: int
22
+
23
+
24
+ class NewMarketEvent(BaseModel):
25
+ market: HexAddress
26
+ marketName: str
27
+ parentMarket: HexAddress
28
+ conditionId: HexBytes
29
+ questionId: HexBytes
30
+ questionsIds: list[HexBytes]
31
+
32
+
33
+ class CreateCategoricalMarketsParams(BaseModel):
34
+ model_config = ConfigDict(populate_by_name=True)
35
+
36
+ market_name: str = Field(..., alias="marketName")
37
+ outcomes: list[str]
38
+ # Only relevant for scalar markets
39
+ question_start: str = Field(alias="questionStart", default="")
40
+ question_end: str = Field(alias="questionEnd", default="")
41
+ outcome_type: str = Field(alias="outcomeType", default="")
42
+
43
+ # Not needed for non-conditional markets.
44
+ parent_outcome: int = Field(alias="parentOutcome", default=0)
45
+ parent_market: HexAddress = Field(alias="parentMarket", default=ADDRESS_ZERO)
46
+
47
+ category: str
48
+ lang: str
49
+ lower_bound: int = Field(alias="lowerBound", default=0)
50
+ upper_bound: int = Field(alias="upperBound", default=0)
51
+ min_bond: Wei = Field(..., alias="minBond")
52
+ opening_time: int = Field(..., alias="openingTime")
53
+ token_names: list[str] = Field(..., alias="tokenNames")
54
+
55
+
56
+ class SeerParentMarket(BaseModel):
57
+ id: HexBytes
@@ -207,6 +207,17 @@ class ContractERC20BaseClass(ContractBaseClass):
207
207
  value: str = self._cache[cache_key]
208
208
  return value
209
209
 
210
+ def allowance(
211
+ self,
212
+ owner: ChecksumAddress,
213
+ for_address: ChecksumAddress,
214
+ web3: Web3 | None = None,
215
+ ) -> int:
216
+ allowance_for_user: int = self.call(
217
+ "allowance", function_params=[owner, for_address], web3=web3
218
+ )
219
+ return allowance_for_user
220
+
210
221
  def approve(
211
222
  self,
212
223
  api_keys: APIKeys,
@@ -4,9 +4,11 @@ from datetime import timedelta
4
4
  import httpx
5
5
  import tenacity
6
6
  from cowdao_cowpy import swap_tokens
7
+ from cowdao_cowpy.common.api.errors import UnexpectedResponseError
7
8
  from cowdao_cowpy.common.chains import Chain
8
9
  from cowdao_cowpy.common.config import SupportedChainId
9
10
  from cowdao_cowpy.common.constants import CowContractAddress
11
+ from cowdao_cowpy.cow.swap import get_order_quote
10
12
  from cowdao_cowpy.order_book.api import OrderBookApi
11
13
  from cowdao_cowpy.order_book.config import Envs, OrderBookAPIConfigFactory
12
14
  from cowdao_cowpy.order_book.generated.model import (
@@ -14,25 +16,24 @@ from cowdao_cowpy.order_book.generated.model import (
14
16
  OrderMetaData,
15
17
  OrderQuoteRequest,
16
18
  OrderQuoteSide1,
17
- OrderQuoteSide3,
18
- OrderQuoteSideKindBuy,
19
19
  OrderQuoteSideKindSell,
20
20
  OrderStatus,
21
21
  TokenAmount,
22
+ OrderQuoteResponse,
22
23
  )
23
24
  from eth_account.signers.local import LocalAccount
24
- from eth_typing.evm import ChecksumAddress
25
+ from tenacity import stop_after_attempt, wait_fixed, retry_if_not_exception_type
25
26
  from web3 import Web3
26
27
 
27
28
  from prediction_market_agent_tooling.config import APIKeys
28
- from prediction_market_agent_tooling.gtypes import ChecksumAddress, Wei
29
+ from prediction_market_agent_tooling.gtypes import ChecksumAddress, Wei, wei_type
29
30
  from prediction_market_agent_tooling.loggers import logger
30
31
  from prediction_market_agent_tooling.tools.contract import ContractERC20OnGnosisChain
31
32
  from prediction_market_agent_tooling.tools.utils import utcnow
32
33
 
33
34
 
34
- class OrderStatusError(Exception):
35
- pass
35
+ class NoLiquidityAvailableOnCowException(Exception):
36
+ """Custom exception for handling case where no liquidity available."""
36
37
 
37
38
 
38
39
  def get_order_book_api(env: Envs, chain: Chain) -> OrderBookApi:
@@ -41,20 +42,17 @@ def get_order_book_api(env: Envs, chain: Chain) -> OrderBookApi:
41
42
 
42
43
 
43
44
  @tenacity.retry(
44
- stop=tenacity.stop_after_attempt(3),
45
- wait=tenacity.wait_fixed(1),
46
- after=lambda x: logger.debug(f"get_sell_token_amount failed, {x.attempt_number=}."),
45
+ stop=stop_after_attempt(3),
46
+ wait=wait_fixed(2),
47
+ retry=retry_if_not_exception_type(NoLiquidityAvailableOnCowException),
47
48
  )
48
- def get_sell_token_amount(
49
- buy_amount: Wei,
49
+ def get_quote(
50
+ amount_wei: Wei,
50
51
  sell_token: ChecksumAddress,
51
52
  buy_token: ChecksumAddress,
52
53
  chain: Chain = Chain.GNOSIS,
53
54
  env: Envs = "prod",
54
- ) -> Wei:
55
- """
56
- Calculate how much of the sell_token is needed to obtain a specified amount of buy_token.
57
- """
55
+ ) -> OrderQuoteResponse:
58
56
  order_book_api = get_order_book_api(env, chain)
59
57
  order_quote_request = OrderQuoteRequest(
60
58
  sellToken=Address(sell_token),
@@ -63,52 +61,49 @@ def get_sell_token_amount(
63
61
  "0x1234567890abcdef1234567890abcdef12345678"
64
62
  ), # Just random address, doesn't matter.
65
63
  )
66
- order_side = OrderQuoteSide3(
67
- kind=OrderQuoteSideKindBuy.buy,
68
- buyAmountAfterFee=TokenAmount(str(buy_amount)),
69
- )
70
- order_quote = asyncio.run(
71
- order_book_api.post_quote(order_quote_request, order_side)
64
+ order_side = OrderQuoteSide1(
65
+ kind=OrderQuoteSideKindSell.sell,
66
+ sellAmountBeforeFee=TokenAmount(str(amount_wei)),
72
67
  )
73
- return Wei(order_quote.quote.sellAmount.root)
74
68
 
69
+ try:
70
+ order_quote = asyncio.run(
71
+ get_order_quote(
72
+ order_quote_request=order_quote_request,
73
+ order_side=order_side,
74
+ order_book_api=order_book_api,
75
+ )
76
+ )
75
77
 
76
- @tenacity.retry(
77
- stop=tenacity.stop_after_attempt(3),
78
- wait=tenacity.wait_fixed(1),
79
- after=lambda x: logger.debug(f"get_buy_token_amount failed, {x.attempt_number=}."),
80
- )
81
- def get_buy_token_amount(
82
- sell_amount: Wei,
78
+ return order_quote
79
+
80
+ except UnexpectedResponseError as e1:
81
+ if "NoLiquidity" in e1.message:
82
+ raise NoLiquidityAvailableOnCowException(e1.message)
83
+ logger.warning(f"Found unexpected Cow response error: {e1}")
84
+ raise
85
+ except Exception as e:
86
+ logger.warning(f"Found unhandled Cow response error: {e}")
87
+ raise
88
+
89
+
90
+ def get_buy_token_amount_else_raise(
91
+ amount_wei: Wei,
83
92
  sell_token: ChecksumAddress,
84
93
  buy_token: ChecksumAddress,
85
94
  chain: Chain = Chain.GNOSIS,
86
95
  env: Envs = "prod",
87
96
  ) -> Wei:
88
- order_book_api = get_order_book_api(env, chain)
89
- order_quote_request = OrderQuoteRequest(
90
- sellToken=Address(sell_token),
91
- buyToken=Address(buy_token),
92
- from_=Address(
93
- "0x1234567890abcdef1234567890abcdef12345678"
94
- ), # Just random address, doesn't matter.
95
- )
96
- order_side = OrderQuoteSide1(
97
- kind=OrderQuoteSideKindSell.sell,
98
- sellAmountBeforeFee=TokenAmount(str(sell_amount)),
99
- )
100
- order_quote = asyncio.run(
101
- order_book_api.post_quote(order_quote_request, order_side)
97
+ order_quote = get_quote(
98
+ amount_wei=amount_wei,
99
+ sell_token=sell_token,
100
+ buy_token=buy_token,
101
+ chain=chain,
102
+ env=env,
102
103
  )
103
- return Wei(order_quote.quote.buyAmount.root)
104
+ return wei_type(order_quote.quote.buyAmount.root)
104
105
 
105
106
 
106
- @tenacity.retry(
107
- stop=tenacity.stop_after_attempt(3),
108
- wait=tenacity.wait_fixed(1),
109
- retry=tenacity.retry_if_not_exception_type((TimeoutError, OrderStatusError)),
110
- after=lambda x: logger.debug(f"swap_tokens_waiting failed, {x.attempt_number=}."),
111
- )
112
107
  def swap_tokens_waiting(
113
108
  amount_wei: Wei,
114
109
  sell_token: ChecksumAddress,
@@ -146,7 +141,7 @@ async def swap_tokens_waiting_async(
146
141
  timeout: timedelta = timedelta(seconds=60),
147
142
  ) -> OrderMetaData:
148
143
  order = await swap_tokens(
149
- amount=amount_wei.value,
144
+ amount=amount_wei,
150
145
  sell_token=sell_token,
151
146
  buy_token=buy_token,
152
147
  account=account,
@@ -169,7 +164,7 @@ async def swap_tokens_waiting_async(
169
164
  OrderStatus.cancelled,
170
165
  OrderStatus.expired,
171
166
  ):
172
- raise OrderStatusError(f"Order {order.uid} failed. {order.url}")
167
+ raise ValueError(f"Order {order.uid} failed. {order.url}")
173
168
 
174
169
  if utcnow() - start_time > timeout:
175
170
  raise TimeoutError(
@@ -10,6 +10,7 @@ from prediction_market_agent_tooling.tools.contract import (
10
10
  ContractERC4626BaseClass,
11
11
  )
12
12
  from prediction_market_agent_tooling.tools.cow.cow_order import (
13
+ get_buy_token_amount_else_raise,
13
14
  get_sell_token_amount,
14
15
  swap_tokens_waiting,
15
16
  )
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: prediction-market-agent-tooling
3
- Version: 0.62.0.dev493
3
+ Version: 0.63.0.dev498
4
4
  Summary: Tools to benchmark, deploy and monitor prediction market agents.
5
5
  Author: Gnosis
6
6
  Requires-Python: >=3.10,<3.13
@@ -21,9 +21,9 @@ prediction_market_agent_tooling/benchmark/agents.py,sha256=B1-uWdyeN4GGKMWGK_-Cc
21
21
  prediction_market_agent_tooling/benchmark/benchmark.py,sha256=MqTiaaJ3cYiOLUVR7OyImLWxcEya3Rl5JyFYW-K0lwM,17097
22
22
  prediction_market_agent_tooling/benchmark/utils.py,sha256=D0MfUkVZllmvcU0VOurk9tcKT7JTtwwOp-63zuCBVuc,2880
23
23
  prediction_market_agent_tooling/config.py,sha256=So5l8KbgmzcCpxzzf13TNrEJPu_4iQnUDhzus6XRvSc,10151
24
- prediction_market_agent_tooling/deploy/agent.py,sha256=DW9edzHDX7QVURMGyOoIHTIvl3Itpbi8i0l5XPrEkbk,24974
24
+ prediction_market_agent_tooling/deploy/agent.py,sha256=OyrhPOjByQOAi1_VWhef7ieKqREjVhvjGHgnUIQc3gI,25877
25
25
  prediction_market_agent_tooling/deploy/agent_example.py,sha256=dIIdZashExWk9tOdyDjw87AuUcGyM7jYxNChYrVK2dM,1001
26
- prediction_market_agent_tooling/deploy/betting_strategy.py,sha256=iU-D7cdcwiCOWbjDt8hin3ZpL633pQbw9GIKRnIR3FY,12494
26
+ prediction_market_agent_tooling/deploy/betting_strategy.py,sha256=aqDLLVt0ip6gmPAJZcz59HdMS69HqsvsqDCiIKhKP4E,12917
27
27
  prediction_market_agent_tooling/deploy/constants.py,sha256=M5ty8URipYMGe_G-RzxRydK3AFL6CyvmqCraJUrLBnE,82
28
28
  prediction_market_agent_tooling/deploy/gcp/deploy.py,sha256=CYUgnfy-9XVk04kkxA_5yp0GE9Mw5caYqlFUZQ2j3ks,3739
29
29
  prediction_market_agent_tooling/deploy/gcp/kubernetes_models.py,sha256=OsPboCFGiZKsvGyntGZHwdqPlLTthITkNF5rJFvGgU8,2582
@@ -34,9 +34,9 @@ prediction_market_agent_tooling/jobs/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeu
34
34
  prediction_market_agent_tooling/jobs/jobs_models.py,sha256=8vYafsK1cqMWQtjBoq9rruroF84xAVD00vBTMWH6QMg,2166
35
35
  prediction_market_agent_tooling/jobs/omen/omen_jobs.py,sha256=Pf6QxPXGyie-2l_wZUjaGPTjZTlpv50_JhP40mULBaU,5048
36
36
  prediction_market_agent_tooling/loggers.py,sha256=MvCkQSJL2_0yErNatqr81sJlc4aOgPzDp9VNrIhKUcc,4140
37
- prediction_market_agent_tooling/markets/agent_market.py,sha256=A8eHhV68RIBUE1eIxVchp3QstzpnAYdZxaANyS6vg68,14866
37
+ prediction_market_agent_tooling/markets/agent_market.py,sha256=1NomilM0GCXcRq_1N_cr2AbSK5ONTowFeRbrhc7V5zE,14929
38
38
  prediction_market_agent_tooling/markets/base_subgraph_handler.py,sha256=7RaYO_4qAmQ6ZGM8oPK2-CkiJfKmV9MxM-rJlduaecU,1971
39
- prediction_market_agent_tooling/markets/blockchain_utils.py,sha256=1iTU_D-Uof0E442qVUhSBCfc1rJNQpDcd3UjSnilYZg,2129
39
+ prediction_market_agent_tooling/markets/blockchain_utils.py,sha256=rm-nh-FuIMuH3AuneRNfkwOfHc7Cs7DnPAcHtuTFcuw,2363
40
40
  prediction_market_agent_tooling/markets/categorize.py,sha256=jsoHWvZk9pU6n17oWSCcCxNNYVwlb_NXsZxKRI7vmsk,1301
41
41
  prediction_market_agent_tooling/markets/data_models.py,sha256=_R9Hr5zwGLpZLPXq0Jo2wZRNRQyOnSi3WVQJZ81syuk,4541
42
42
  prediction_market_agent_tooling/markets/manifold/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
@@ -51,7 +51,7 @@ prediction_market_agent_tooling/markets/metaculus/data_models.py,sha256=FaBCTPPe
51
51
  prediction_market_agent_tooling/markets/metaculus/metaculus.py,sha256=86TIx6cavEWc8Cv4KpZxSvwiSw9oFybXE3YB49pg-CA,4377
52
52
  prediction_market_agent_tooling/markets/omen/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
53
53
  prediction_market_agent_tooling/markets/omen/data_models.py,sha256=URvplDcTLeL6vfpkSgi8ln2iEKxt8w5qf6mTZkSyrCo,29189
54
- prediction_market_agent_tooling/markets/omen/omen.py,sha256=_Vt1_w_A6-FHPVgNclwSh2qpABBXXTJ2wKPtcFfrDTQ,51893
54
+ prediction_market_agent_tooling/markets/omen/omen.py,sha256=Jf2qSJJn0UUISpi24xYRwoVycGuYE42kZ2z1HRGl43w,51927
55
55
  prediction_market_agent_tooling/markets/omen/omen_constants.py,sha256=D9oflYKafLQiHYtB5sScMHqmXyzM8JP8J0yATmc4SQQ,233
56
56
  prediction_market_agent_tooling/markets/omen/omen_contracts.py,sha256=bCC9A7ZTJxMDJcPbl3jof6HcAGGHv1BrFq3RRWbkQ_c,28739
57
57
  prediction_market_agent_tooling/markets/omen/omen_resolving.py,sha256=Cyi9Ci-Z-K8WCZWVLs9oSuJC6qRobi_CpqDCno_5F-0,10238
@@ -61,10 +61,12 @@ prediction_market_agent_tooling/markets/polymarket/data_models.py,sha256=utGN-Lh
61
61
  prediction_market_agent_tooling/markets/polymarket/data_models_web.py,sha256=LVEsNw2nUx5poiU1m803NNqG5-fs8-MODQRyGLqy4mE,12585
62
62
  prediction_market_agent_tooling/markets/polymarket/polymarket.py,sha256=6rc9qulPl90MxXKB55XiiWKLhjfAyG_eUzAlqpq1UIE,3339
63
63
  prediction_market_agent_tooling/markets/polymarket/utils.py,sha256=8kTeVjXPcXC6DkDvWYsZQLY7x8DS6CEp_yznSEazsNU,2037
64
- prediction_market_agent_tooling/markets/seer/data_models.py,sha256=qo8qH-3j0czH8zrgHnaYyB5-f38zZcl9MNtQfXi_JZM,8192
65
- prediction_market_agent_tooling/markets/seer/seer.py,sha256=l9H7AYRje4bEXMdVzw83cseY46BLgOAgiJarE_8faHk,13189
66
- prediction_market_agent_tooling/markets/seer/seer_contracts.py,sha256=XOEhaL5wrKEg7P-xg1mW5mJXVfeALuflJOvqAeuwrWM,2717
67
- prediction_market_agent_tooling/markets/seer/seer_subgraph_handler.py,sha256=o5Sh_6bpiJQb1KnW3rf4cBqN2PM8IdggQARdBeDMvIE,8725
64
+ prediction_market_agent_tooling/markets/seer/data_models.py,sha256=Rj3Ajs9ZPr_kbCrgtdlsvzgi3ri6e-b2UcSkX2qQ7rs,6519
65
+ prediction_market_agent_tooling/markets/seer/price_manager.py,sha256=S-pAf7a2nfSNoM53xv8p6lV3ipfq0930ivw6IQRcZqw,4759
66
+ prediction_market_agent_tooling/markets/seer/seer.py,sha256=G9QkozlKj4YYgBEJlbWIgdgSvDFeW1OGZlaRZuXqAyY,15314
67
+ prediction_market_agent_tooling/markets/seer/seer_contracts.py,sha256=JZF4DY8S9P4xzz1pA2y7G2M9WRFrJSN9wx928IIo77E,2726
68
+ prediction_market_agent_tooling/markets/seer/seer_subgraph_handler.py,sha256=bAuMRBmPNax20RvB448U7_OfBtfRf6P6TouoYDu_OfM,9867
69
+ prediction_market_agent_tooling/markets/seer/subgraph_data_models.py,sha256=NscHqrfhFVtiHS3edIWLwwE32bBkEzyTZNl7fCvqaqQ,1644
68
70
  prediction_market_agent_tooling/monitor/financial_metrics/financial_metrics.py,sha256=fjIgjDIx5MhH5mwf7S0cspLOOSU3elYLhGYoIiM26mU,2746
69
71
  prediction_market_agent_tooling/monitor/markets/manifold.py,sha256=TS4ERwTfQnot8dhekNyVNhJYf5ysYsjF-9v5_kM3aVI,3334
70
72
  prediction_market_agent_tooling/monitor/markets/metaculus.py,sha256=LOnyWWBFdg10-cTWdb76nOsNjDloO8OfMT85GBzRCFI,1455
@@ -84,10 +86,9 @@ prediction_market_agent_tooling/tools/betting_strategies/utils.py,sha256=68zFWUj
84
86
  prediction_market_agent_tooling/tools/caches/db_cache.py,sha256=dB8LNs2JvVRaFCeAKRmIQRwiirsMgtL31he8051wM-g,11431
85
87
  prediction_market_agent_tooling/tools/caches/inmemory_cache.py,sha256=ZW5iI5rmjqeAebu5T7ftRnlkxiL02IC-MxCfDB80x7w,1506
86
88
  prediction_market_agent_tooling/tools/caches/serializers.py,sha256=vFDx4fsPxclXp2q0sv27j4al_M_Tj9aR2JJP-xNHQXA,2151
87
- prediction_market_agent_tooling/tools/contract.py,sha256=PJaC_Mc06vowyHD6f4MNQuEfo-h0H-Lh741YzRuemUk,21018
89
+ prediction_market_agent_tooling/tools/contract.py,sha256=1ZFp_VoqTjM8cqOfAhco2Ht0DTqakjhZpuZUrAXr28Q,21332
88
90
  prediction_market_agent_tooling/tools/costs.py,sha256=EaAJ7v9laD4VEV3d8B44M4u3_oEO_H16jRVCdoZ93Uw,954
89
- prediction_market_agent_tooling/tools/cow/cow_manager.py,sha256=ZX6K0ZKMpcgiDsIrv4wtZAPg-LTozIbvOyQFyVgDpMA,3812
90
- prediction_market_agent_tooling/tools/cow/cow_order.py,sha256=4BV9t_RZU07SxwAzjTVkXKoQtm9l-roFHJR_cE8O04A,5837
91
+ prediction_market_agent_tooling/tools/cow/cow_order.py,sha256=F6ZPDGCR9ymLflNc8MNTjt-0TJiuM3gBcZ10Ifk-aBE,5550
91
92
  prediction_market_agent_tooling/tools/custom_exceptions.py,sha256=Fh8z1fbwONvP4-j7AmV_PuEcoqb6-QXa9PJ9m7guMcM,93
92
93
  prediction_market_agent_tooling/tools/datetime_utc.py,sha256=8_WackjtjC8zHXrhQFTGQ6e6Fz_6llWoKR4CSFvIv9I,2766
93
94
  prediction_market_agent_tooling/tools/db/db_manager.py,sha256=GtzHH1NLl8HwqC8Z7s6eTlIQXuV0blxfaV2PeQrBnfQ,3013
@@ -112,7 +113,7 @@ prediction_market_agent_tooling/tools/singleton.py,sha256=CiIELUiI-OeS7U7eeHEt0r
112
113
  prediction_market_agent_tooling/tools/streamlit_user_login.py,sha256=NXEqfjT9Lc9QtliwSGRASIz1opjQ7Btme43H4qJbzgE,3010
113
114
  prediction_market_agent_tooling/tools/tavily/tavily_models.py,sha256=5ldQs1pZe6uJ5eDAuP4OLpzmcqYShlIV67kttNFvGS0,342
114
115
  prediction_market_agent_tooling/tools/tavily/tavily_search.py,sha256=pPs0qZNfJ7G-1ajfz0iaWOBQyiC0TbcShfrW8T39jtg,3859
115
- prediction_market_agent_tooling/tools/tokens/auto_deposit.py,sha256=MWIRHYCN-7o2uawEBwnBNhlzme1kLyX_5Q2zg7UIegQ,6700
116
+ prediction_market_agent_tooling/tools/tokens/auto_deposit.py,sha256=dO-2XUcsCVvuRpvSN4dr1ZEoVS3Ee9soisLQI3bX8CU,6737
116
117
  prediction_market_agent_tooling/tools/tokens/auto_withdraw.py,sha256=22g0SIVmLlgITpdt3kPhJOw0sU4OPeBuYk_7xCrQr9U,2491
117
118
  prediction_market_agent_tooling/tools/tokens/main_token.py,sha256=1rbwpdCusPgQIVFuo3m00nBZ_b2lCAoFVm67i-YDcEw,812
118
119
  prediction_market_agent_tooling/tools/tokens/token_utils.py,sha256=zwV-jGFkPJu4-IslXOUqnsNQjzh_9CrfkruDQL0dq0c,1381
@@ -120,8 +121,8 @@ prediction_market_agent_tooling/tools/tokens/usd.py,sha256=l4vPemtF6Zcwh8HNXH3-L
120
121
  prediction_market_agent_tooling/tools/transaction_cache.py,sha256=K5YKNL2_tR10Iw2TD9fuP-CTGpBbZtNdgbd0B_R7pjg,1814
121
122
  prediction_market_agent_tooling/tools/utils.py,sha256=1xsyBBJfiEdSoMlceB2F8o2sCb6Z8-qNz11pEJFrdyE,6566
122
123
  prediction_market_agent_tooling/tools/web3_utils.py,sha256=eYCc1iWAVtqDKUPTwnMUHuYolPdwh_OTiM3-AdRgDp4,12198
123
- prediction_market_agent_tooling-0.62.0.dev493.dist-info/LICENSE,sha256=6or154nLLU6bELzjh0mCreFjt0m2v72zLi3yHE0QbeE,7650
124
- prediction_market_agent_tooling-0.62.0.dev493.dist-info/METADATA,sha256=vJ5R6V9K45e251VIx5omIKqUswyEg_i7aU-CsNeHZ9Q,8696
125
- prediction_market_agent_tooling-0.62.0.dev493.dist-info/WHEEL,sha256=XbeZDeTWKc1w7CSIyre5aMDU_-PohRwTQceYnisIYYY,88
126
- prediction_market_agent_tooling-0.62.0.dev493.dist-info/entry_points.txt,sha256=m8PukHbeH5g0IAAmOf_1Ahm-sGAMdhSSRQmwtpmi2s8,81
127
- prediction_market_agent_tooling-0.62.0.dev493.dist-info/RECORD,,
124
+ prediction_market_agent_tooling-0.63.0.dev498.dist-info/LICENSE,sha256=6or154nLLU6bELzjh0mCreFjt0m2v72zLi3yHE0QbeE,7650
125
+ prediction_market_agent_tooling-0.63.0.dev498.dist-info/METADATA,sha256=AF85f__OPLoNCAibGYaUWCZzHky2cfGdQKeZsBwLQG4,8696
126
+ prediction_market_agent_tooling-0.63.0.dev498.dist-info/WHEEL,sha256=XbeZDeTWKc1w7CSIyre5aMDU_-PohRwTQceYnisIYYY,88
127
+ prediction_market_agent_tooling-0.63.0.dev498.dist-info/entry_points.txt,sha256=m8PukHbeH5g0IAAmOf_1Ahm-sGAMdhSSRQmwtpmi2s8,81
128
+ prediction_market_agent_tooling-0.63.0.dev498.dist-info/RECORD,,
@@ -1,112 +0,0 @@
1
- import asyncio
2
-
3
- from cowdao_cowpy.common.api.errors import UnexpectedResponseError
4
- from cowdao_cowpy.common.config import SupportedChainId
5
- from cowdao_cowpy.cow.swap import get_order_quote
6
- from cowdao_cowpy.order_book.api import OrderBookApi
7
- from cowdao_cowpy.order_book.config import Envs, OrderBookAPIConfigFactory
8
- from cowdao_cowpy.order_book.generated.model import (
9
- Address,
10
- OrderMetaData,
11
- OrderQuoteRequest,
12
- OrderQuoteResponse,
13
- OrderQuoteSide1,
14
- OrderQuoteSideKindSell,
15
- )
16
- from cowdao_cowpy.order_book.generated.model import TokenAmount as TokenAmountCow
17
- from tenacity import retry, retry_if_not_exception_type, stop_after_attempt, wait_fixed
18
- from web3 import Web3
19
- from web3.constants import ADDRESS_ZERO
20
-
21
- from prediction_market_agent_tooling.config import APIKeys
22
- from prediction_market_agent_tooling.gtypes import ChecksumAddress, CollateralToken, Wei
23
- from prediction_market_agent_tooling.loggers import logger
24
- from prediction_market_agent_tooling.tools.cow.cow_order import swap_tokens_waiting
25
-
26
- COW_ENV: Envs = "prod"
27
-
28
-
29
- class NoLiquidityAvailableOnCowException(Exception):
30
- """Custom exception for handling case where no liquidity available."""
31
-
32
-
33
- class CowManager:
34
- def __init__(self) -> None:
35
- self.order_book_api = OrderBookApi(
36
- OrderBookAPIConfigFactory.get_config(COW_ENV, SupportedChainId.GNOSIS_CHAIN)
37
- )
38
- self.precision = 18 # number of token decimals from ERC1155 wrapped tokens.
39
-
40
- @retry(
41
- stop=stop_after_attempt(2),
42
- wait=wait_fixed(2),
43
- retry=retry_if_not_exception_type(NoLiquidityAvailableOnCowException),
44
- )
45
- def get_quote(
46
- self,
47
- collateral_token: ChecksumAddress,
48
- buy_token: ChecksumAddress,
49
- sell_amount: Wei,
50
- ) -> OrderQuoteResponse:
51
- """
52
- Quote the price for a sell order.
53
-
54
- Parameters:
55
- - collateral_token: The token being sold.
56
- - buy_token: The token being bought.
57
- - sell_amount: The amount of collateral to sell in atoms.
58
-
59
- Returns:
60
- - An OrderQuoteResponse containing the quote information.
61
-
62
- Raises:
63
- - NoLiquidityAvailableOnCowException if no liquidity is available on CoW.
64
- """
65
-
66
- order_quote_request = OrderQuoteRequest(
67
- buyToken=Address(buy_token),
68
- sellToken=Address(collateral_token),
69
- from_=Address(ADDRESS_ZERO),
70
- )
71
-
72
- order_side = OrderQuoteSide1(
73
- kind=OrderQuoteSideKindSell.sell,
74
- sellAmountBeforeFee=TokenAmountCow(str(sell_amount)),
75
- )
76
- try:
77
- return asyncio.run(
78
- get_order_quote(
79
- order_quote_request=order_quote_request,
80
- order_side=order_side,
81
- order_book_api=self.order_book_api,
82
- )
83
- )
84
-
85
- except UnexpectedResponseError as e1:
86
- if "NoLiquidity" in e1.message:
87
- raise NoLiquidityAvailableOnCowException(e1.message)
88
- logger.warning(f"Found unexpected Cow response error: {e1}")
89
- raise
90
- except Exception as e:
91
- logger.warning(f"Found unhandled Cow response error: {e}")
92
- raise
93
-
94
- @staticmethod
95
- def swap(
96
- amount: CollateralToken,
97
- sell_token: ChecksumAddress,
98
- buy_token: ChecksumAddress,
99
- api_keys: APIKeys,
100
- web3: Web3 | None = None,
101
- ) -> OrderMetaData:
102
- order_metadata = swap_tokens_waiting(
103
- amount_wei=amount.as_wei,
104
- sell_token=sell_token,
105
- buy_token=buy_token,
106
- api_keys=api_keys,
107
- web3=web3,
108
- )
109
- logger.debug(
110
- f"Purchased {buy_token} in exchange for {sell_token}. Order details {order_metadata}"
111
- )
112
- return order_metadata