prediction-market-agent-tooling 0.68.0.dev999__py3-none-any.whl → 0.68.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.
- prediction_market_agent_tooling/chains.py +1 -0
- prediction_market_agent_tooling/config.py +37 -2
- prediction_market_agent_tooling/deploy/agent.py +13 -19
- prediction_market_agent_tooling/deploy/betting_strategy.py +11 -3
- prediction_market_agent_tooling/jobs/jobs_models.py +2 -2
- prediction_market_agent_tooling/jobs/omen/omen_jobs.py +3 -3
- prediction_market_agent_tooling/markets/agent_market.py +16 -9
- prediction_market_agent_tooling/markets/blockchain_utils.py +3 -3
- prediction_market_agent_tooling/markets/omen/data_models.py +3 -18
- prediction_market_agent_tooling/markets/omen/omen.py +26 -11
- prediction_market_agent_tooling/markets/omen/omen_contracts.py +2 -196
- prediction_market_agent_tooling/markets/omen/omen_resolving.py +2 -2
- prediction_market_agent_tooling/markets/omen/omen_subgraph_handler.py +13 -11
- prediction_market_agent_tooling/markets/polymarket/api.py +35 -1
- prediction_market_agent_tooling/markets/polymarket/clob_manager.py +156 -0
- prediction_market_agent_tooling/markets/polymarket/constants.py +15 -0
- prediction_market_agent_tooling/markets/polymarket/data_models.py +33 -5
- prediction_market_agent_tooling/markets/polymarket/polymarket.py +247 -18
- prediction_market_agent_tooling/markets/polymarket/polymarket_contracts.py +35 -0
- prediction_market_agent_tooling/markets/polymarket/polymarket_subgraph_handler.py +2 -1
- prediction_market_agent_tooling/markets/seer/data_models.py +1 -1
- prediction_market_agent_tooling/markets/seer/price_manager.py +69 -1
- prediction_market_agent_tooling/markets/seer/seer.py +35 -20
- prediction_market_agent_tooling/markets/seer/seer_subgraph_handler.py +7 -3
- prediction_market_agent_tooling/markets/seer/subgraph_data_models.py +2 -0
- prediction_market_agent_tooling/tools/contract.py +236 -4
- prediction_market_agent_tooling/tools/cow/cow_order.py +13 -8
- prediction_market_agent_tooling/tools/hexbytes_custom.py +3 -9
- prediction_market_agent_tooling/tools/tokens/auto_deposit.py +1 -1
- prediction_market_agent_tooling/tools/tokens/usd.py +5 -2
- prediction_market_agent_tooling/tools/web3_utils.py +9 -4
- {prediction_market_agent_tooling-0.68.0.dev999.dist-info → prediction_market_agent_tooling-0.68.1.dist-info}/METADATA +8 -7
- {prediction_market_agent_tooling-0.68.0.dev999.dist-info → prediction_market_agent_tooling-0.68.1.dist-info}/RECORD +36 -34
- prediction_market_agent_tooling/markets/polymarket/data_models_web.py +0 -366
- {prediction_market_agent_tooling-0.68.0.dev999.dist-info → prediction_market_agent_tooling-0.68.1.dist-info}/LICENSE +0 -0
- {prediction_market_agent_tooling-0.68.0.dev999.dist-info → prediction_market_agent_tooling-0.68.1.dist-info}/WHEEL +0 -0
- {prediction_market_agent_tooling-0.68.0.dev999.dist-info → prediction_market_agent_tooling-0.68.1.dist-info}/entry_points.txt +0 -0
@@ -1,11 +1,19 @@
|
|
1
1
|
import typing as t
|
2
2
|
|
3
|
+
import cachetools
|
4
|
+
from web3 import Web3
|
5
|
+
|
6
|
+
from prediction_market_agent_tooling.config import APIKeys, RPCConfig
|
3
7
|
from prediction_market_agent_tooling.gtypes import (
|
4
8
|
USD,
|
9
|
+
ChecksumAddress,
|
5
10
|
CollateralToken,
|
6
11
|
HexBytes,
|
7
12
|
OutcomeStr,
|
13
|
+
OutcomeToken,
|
8
14
|
Probability,
|
15
|
+
Wei,
|
16
|
+
xDai,
|
9
17
|
)
|
10
18
|
from prediction_market_agent_tooling.loggers import logger
|
11
19
|
from prediction_market_agent_tooling.markets.agent_market import (
|
@@ -13,27 +21,45 @@ from prediction_market_agent_tooling.markets.agent_market import (
|
|
13
21
|
ConditionalFilterType,
|
14
22
|
FilterBy,
|
15
23
|
MarketFees,
|
24
|
+
ProcessedMarket,
|
16
25
|
QuestionType,
|
17
26
|
SortBy,
|
18
27
|
)
|
19
|
-
from prediction_market_agent_tooling.markets.data_models import
|
28
|
+
from prediction_market_agent_tooling.markets.data_models import (
|
29
|
+
ExistingPosition,
|
30
|
+
Resolution,
|
31
|
+
)
|
20
32
|
from prediction_market_agent_tooling.markets.polymarket.api import (
|
21
33
|
PolymarketOrderByEnum,
|
22
34
|
get_polymarkets_with_pagination,
|
35
|
+
get_user_positions,
|
36
|
+
)
|
37
|
+
from prediction_market_agent_tooling.markets.polymarket.clob_manager import (
|
38
|
+
ClobManager,
|
39
|
+
PolymarketPriceSideEnum,
|
40
|
+
)
|
41
|
+
from prediction_market_agent_tooling.markets.polymarket.constants import (
|
42
|
+
POLYMARKET_TINY_BET_AMOUNT,
|
23
43
|
)
|
24
44
|
from prediction_market_agent_tooling.markets.polymarket.data_models import (
|
45
|
+
POLYMARKET_BASE_URL,
|
25
46
|
PolymarketGammaResponseDataItem,
|
26
47
|
)
|
27
|
-
from prediction_market_agent_tooling.markets.polymarket.
|
28
|
-
|
48
|
+
from prediction_market_agent_tooling.markets.polymarket.polymarket_contracts import (
|
49
|
+
USDCeContract,
|
29
50
|
)
|
30
51
|
from prediction_market_agent_tooling.markets.polymarket.polymarket_subgraph_handler import (
|
31
52
|
ConditionSubgraphModel,
|
32
53
|
PolymarketSubgraphHandler,
|
33
54
|
)
|
34
55
|
from prediction_market_agent_tooling.tools.datetime_utc import DatetimeUTC
|
56
|
+
from prediction_market_agent_tooling.tools.tokens.usd import get_token_in_usd
|
35
57
|
from prediction_market_agent_tooling.tools.utils import check_not_none
|
36
58
|
|
59
|
+
SHARED_CACHE: cachetools.TTLCache[t.Hashable, t.Any] = cachetools.TTLCache(
|
60
|
+
maxsize=256, ttl=10 * 60
|
61
|
+
)
|
62
|
+
|
37
63
|
|
38
64
|
class PolymarketAgentMarket(AgentMarket):
|
39
65
|
"""
|
@@ -47,6 +73,15 @@ class PolymarketAgentMarket(AgentMarket):
|
|
47
73
|
# But then in the new subgraph API, they have `fee: BigInt! (Percentage fee of trades taken by market maker. A 2% fee is represented as 2*10^16)`.
|
48
74
|
# TODO: Check out the fees while integrating the subgraph API or if we implement placing of bets on Polymarket.
|
49
75
|
fees: MarketFees = MarketFees.get_zero_fees()
|
76
|
+
condition_id: HexBytes
|
77
|
+
liquidity_usd: USD
|
78
|
+
token_ids: list[int]
|
79
|
+
closed_flag_from_polymarket: bool
|
80
|
+
active_flag_from_polymarket: bool
|
81
|
+
|
82
|
+
@staticmethod
|
83
|
+
def collateral_token_address() -> ChecksumAddress:
|
84
|
+
return USDCeContract().address
|
50
85
|
|
51
86
|
@staticmethod
|
52
87
|
def build_resolution_from_condition(
|
@@ -73,7 +108,7 @@ class PolymarketAgentMarket(AgentMarket):
|
|
73
108
|
if len(payout_numerator_indices_gt_0) != 1:
|
74
109
|
# These cases involve multi-categorical resolution (to be implemented https://github.com/gnosis/prediction-market-agent-tooling/issues/770)
|
75
110
|
logger.warning(
|
76
|
-
f"Only binary markets are supported. Got payout numerators: {condition_model.payoutNumerators} for condition_id {condition_id.
|
111
|
+
f"Only binary markets are supported. Got payout numerators: {condition_model.payoutNumerators} for condition_id {condition_id.to_0x_hex()}"
|
77
112
|
)
|
78
113
|
return Resolution(outcome=None, invalid=False)
|
79
114
|
|
@@ -81,42 +116,68 @@ class PolymarketAgentMarket(AgentMarket):
|
|
81
116
|
resolved_outcome = outcomes[payout_numerator_indices_gt_0[0]]
|
82
117
|
return Resolution.from_answer(resolved_outcome)
|
83
118
|
|
119
|
+
def get_token_id_for_outcome(self, outcome: OutcomeStr) -> int:
|
120
|
+
outcome_idx = self.outcomes.index(outcome)
|
121
|
+
return self.token_ids[outcome_idx]
|
122
|
+
|
84
123
|
@staticmethod
|
85
124
|
def from_data_model(
|
86
125
|
model: PolymarketGammaResponseDataItem,
|
87
126
|
condition_model_dict: dict[HexBytes, ConditionSubgraphModel],
|
88
|
-
) -> "PolymarketAgentMarket":
|
127
|
+
) -> t.Optional["PolymarketAgentMarket"]:
|
89
128
|
# If len(model.markets) > 0, this denotes a categorical market.
|
90
129
|
markets = check_not_none(model.markets)
|
91
130
|
outcomes = markets[0].outcomes_list
|
92
131
|
outcome_prices = markets[0].outcome_prices
|
93
132
|
if not outcome_prices:
|
94
|
-
|
95
|
-
|
133
|
+
logger.info(f"Market has no outcome prices. Skipping. {model=}")
|
134
|
+
return None
|
135
|
+
|
96
136
|
probabilities = {o: Probability(op) for o, op in zip(outcomes, outcome_prices)}
|
97
137
|
|
138
|
+
condition_id = markets[0].conditionId
|
98
139
|
resolution = PolymarketAgentMarket.build_resolution_from_condition(
|
99
|
-
condition_id=
|
140
|
+
condition_id=condition_id,
|
100
141
|
condition_model_dict=condition_model_dict,
|
101
142
|
outcomes=outcomes,
|
102
143
|
)
|
103
144
|
|
104
145
|
return PolymarketAgentMarket(
|
105
146
|
id=model.id,
|
147
|
+
condition_id=condition_id,
|
106
148
|
question=model.title,
|
107
149
|
description=model.description,
|
108
150
|
outcomes=outcomes,
|
109
151
|
resolution=resolution,
|
110
152
|
created_time=model.startDate,
|
111
153
|
close_time=model.endDate,
|
154
|
+
closed_flag_from_polymarket=model.closed,
|
155
|
+
active_flag_from_polymarket=model.active,
|
112
156
|
url=model.url,
|
113
157
|
volume=CollateralToken(model.volume) if model.volume else None,
|
114
158
|
outcome_token_pool=None,
|
115
159
|
probabilities=probabilities,
|
160
|
+
liquidity_usd=USD(model.liquidity)
|
161
|
+
if model.liquidity is not None
|
162
|
+
else USD(0),
|
163
|
+
token_ids=markets[0].token_ids,
|
116
164
|
)
|
117
165
|
|
118
166
|
def get_tiny_bet_amount(self) -> CollateralToken:
|
119
|
-
|
167
|
+
return CollateralToken(POLYMARKET_TINY_BET_AMOUNT.value)
|
168
|
+
|
169
|
+
def get_token_in_usd(self, x: CollateralToken) -> USD:
|
170
|
+
return get_token_in_usd(x, self.collateral_token_address())
|
171
|
+
|
172
|
+
@staticmethod
|
173
|
+
def get_trade_balance(api_keys: APIKeys, web3: Web3 | None = None) -> USD:
|
174
|
+
usdc_balance_wei = USDCeContract().balanceOf(
|
175
|
+
for_address=api_keys.public_key, web3=web3
|
176
|
+
)
|
177
|
+
return USD(usdc_balance_wei.value * 1e-6)
|
178
|
+
|
179
|
+
def get_liquidity(self, web3: Web3 | None = None) -> CollateralToken:
|
180
|
+
return CollateralToken(self.liquidity_usd.value)
|
120
181
|
|
121
182
|
def place_bet(self, outcome: OutcomeStr, amount: USD) -> str:
|
122
183
|
raise NotImplementedError("TODO: Implement to allow betting on Polymarket.")
|
@@ -146,6 +207,7 @@ class PolymarketAgentMarket(AgentMarket):
|
|
146
207
|
match sort_by:
|
147
208
|
case SortBy.NEWEST:
|
148
209
|
order_by = PolymarketOrderByEnum.START_DATE
|
210
|
+
ascending = False
|
149
211
|
case SortBy.CLOSING_SOONEST:
|
150
212
|
ascending = True
|
151
213
|
order_by = PolymarketOrderByEnum.END_DATE
|
@@ -168,15 +230,182 @@ class PolymarketAgentMarket(AgentMarket):
|
|
168
230
|
)
|
169
231
|
|
170
232
|
condition_models = PolymarketSubgraphHandler().get_conditions(
|
171
|
-
condition_ids=
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
|
233
|
+
condition_ids=list(
|
234
|
+
set(
|
235
|
+
[
|
236
|
+
market.markets[0].conditionId
|
237
|
+
for market in markets
|
238
|
+
if market.markets is not None
|
239
|
+
]
|
240
|
+
)
|
241
|
+
)
|
176
242
|
)
|
177
243
|
condition_models_dict = {c.id: c for c in condition_models}
|
178
244
|
|
179
|
-
|
180
|
-
|
181
|
-
|
182
|
-
|
245
|
+
result_markets: list[PolymarketAgentMarket] = []
|
246
|
+
for m in markets:
|
247
|
+
market = PolymarketAgentMarket.from_data_model(m, condition_models_dict)
|
248
|
+
if market is not None:
|
249
|
+
result_markets.append(market)
|
250
|
+
return result_markets
|
251
|
+
|
252
|
+
def ensure_min_native_balance(
|
253
|
+
self,
|
254
|
+
min_required_balance: xDai,
|
255
|
+
multiplier: float = 3.0,
|
256
|
+
web3: Web3 | None = None,
|
257
|
+
) -> None:
|
258
|
+
balance_collateral = USDCeContract().balanceOf(
|
259
|
+
for_address=APIKeys().public_key, web3=web3
|
260
|
+
)
|
261
|
+
# USDC has 6 decimals, xDAI has 18. We convert from Wei into atomic units.
|
262
|
+
balance_collateral_atomic = CollateralToken(float(balance_collateral) / 1e6)
|
263
|
+
if balance_collateral_atomic < min_required_balance.as_token:
|
264
|
+
raise EnvironmentError(
|
265
|
+
f"USDC balance {balance_collateral_atomic} < {min_required_balance.as_token=}"
|
266
|
+
)
|
267
|
+
|
268
|
+
@staticmethod
|
269
|
+
def redeem_winnings(api_keys: APIKeys) -> None:
|
270
|
+
# ToDo - implement me - https://github.com/gnosis/prediction-market-agent-tooling/issues/824
|
271
|
+
pass
|
272
|
+
|
273
|
+
@staticmethod
|
274
|
+
def verify_operational_balance(api_keys: APIKeys) -> bool:
|
275
|
+
"""Method for checking if agent has enough funds to pay for gas fees."""
|
276
|
+
web3 = RPCConfig().get_polygon_web3()
|
277
|
+
pol_balance: Wei = Wei(web3.eth.get_balance(api_keys.public_key))
|
278
|
+
return pol_balance > Wei(int(0.001 * 1e18))
|
279
|
+
|
280
|
+
def store_prediction(
|
281
|
+
self,
|
282
|
+
processed_market: ProcessedMarket | None,
|
283
|
+
keys: APIKeys,
|
284
|
+
agent_name: str,
|
285
|
+
) -> None:
|
286
|
+
pass
|
287
|
+
|
288
|
+
def store_trades(
|
289
|
+
self,
|
290
|
+
traded_market: ProcessedMarket | None,
|
291
|
+
keys: APIKeys,
|
292
|
+
agent_name: str,
|
293
|
+
web3: Web3 | None = None,
|
294
|
+
) -> None:
|
295
|
+
logger.info("Storing trades deactivated for Polymarket.")
|
296
|
+
# Understand how market_id can be represented.
|
297
|
+
# Condition_id could work but length doesn't seem to match.
|
298
|
+
|
299
|
+
@classmethod
|
300
|
+
def get_user_url(cls, keys: APIKeys) -> str:
|
301
|
+
return f"https://polymarket.com/{keys.public_key}"
|
302
|
+
|
303
|
+
def get_position(
|
304
|
+
self, user_id: str, web3: Web3 | None = None
|
305
|
+
) -> ExistingPosition | None:
|
306
|
+
"""
|
307
|
+
Fetches position from the user in a given market.
|
308
|
+
"""
|
309
|
+
positions = get_user_positions(
|
310
|
+
user_id=Web3.to_checksum_address(user_id), condition_ids=[self.condition_id]
|
311
|
+
)
|
312
|
+
if not positions:
|
313
|
+
return None
|
314
|
+
|
315
|
+
amounts_ot = {i: OutcomeToken(0) for i in self.outcomes}
|
316
|
+
amounts_potential = {i: USD(0) for i in self.outcomes}
|
317
|
+
amounts_current = {i: USD(0) for i in self.outcomes}
|
318
|
+
|
319
|
+
for p in positions:
|
320
|
+
if p.conditionId != self.condition_id.to_0x_hex():
|
321
|
+
continue
|
322
|
+
|
323
|
+
amounts_potential[OutcomeStr(p.outcome)] = USD(p.size)
|
324
|
+
amounts_ot[OutcomeStr(p.outcome)] = OutcomeToken(p.size)
|
325
|
+
amounts_current[OutcomeStr(p.outcome)] = USD(p.currentValue)
|
326
|
+
|
327
|
+
return ExistingPosition(
|
328
|
+
amounts_potential=amounts_potential,
|
329
|
+
amounts_ot=amounts_ot,
|
330
|
+
market_id=self.id,
|
331
|
+
amounts_current=amounts_current,
|
332
|
+
)
|
333
|
+
|
334
|
+
def can_be_traded(self) -> bool:
|
335
|
+
return (
|
336
|
+
self.active_flag_from_polymarket
|
337
|
+
and not self.closed_flag_from_polymarket
|
338
|
+
and self.liquidity_usd
|
339
|
+
> USD(5) # we conservatively require some positive liquidity to trade on
|
340
|
+
)
|
341
|
+
|
342
|
+
def get_buy_token_amount(
|
343
|
+
self, bet_amount: USD | CollateralToken, outcome_str: OutcomeStr
|
344
|
+
) -> OutcomeToken:
|
345
|
+
"""Returns number of outcome tokens returned for a given bet expressed in collateral units."""
|
346
|
+
|
347
|
+
if outcome_str not in self.outcomes:
|
348
|
+
raise ValueError(
|
349
|
+
f"Outcome {outcome_str} not found in market outcomes {self.outcomes}"
|
350
|
+
)
|
351
|
+
|
352
|
+
token_id = self.get_token_id_for_outcome(outcome_str)
|
353
|
+
|
354
|
+
price = ClobManager(APIKeys()).get_token_price(
|
355
|
+
token_id=token_id, side=PolymarketPriceSideEnum.BUY
|
356
|
+
)
|
357
|
+
if not price:
|
358
|
+
raise ValueError(
|
359
|
+
f"Could not get price for outcome {outcome_str} with token_id {token_id}"
|
360
|
+
)
|
361
|
+
|
362
|
+
# we work with floats since USD and Collateral are the same on Polymarket
|
363
|
+
buy_token_amount = bet_amount.value / price.value
|
364
|
+
logger.info(f"Buy token amount: {buy_token_amount=}")
|
365
|
+
return OutcomeToken(buy_token_amount)
|
366
|
+
|
367
|
+
def buy_tokens(self, outcome: OutcomeStr, amount: USD) -> str:
|
368
|
+
clob_manager = ClobManager(APIKeys())
|
369
|
+
token_id = self.get_token_id_for_outcome(outcome)
|
370
|
+
|
371
|
+
created_order = clob_manager.place_buy_market_order(
|
372
|
+
token_id=token_id, usdc_amount=amount
|
373
|
+
)
|
374
|
+
if not created_order.success:
|
375
|
+
raise ValueError(f"Error creating order: {created_order}")
|
376
|
+
|
377
|
+
return created_order.transactionsHashes[0].to_0x_hex()
|
378
|
+
|
379
|
+
def sell_tokens(
|
380
|
+
self,
|
381
|
+
outcome: OutcomeStr,
|
382
|
+
amount: USD | OutcomeToken,
|
383
|
+
api_keys: APIKeys | None = None,
|
384
|
+
) -> str:
|
385
|
+
"""
|
386
|
+
Polymarket's API expect shares to be sold. 1 share == 1 outcome token / 1e6.
|
387
|
+
The number of outcome tokens matches the `balanceOf` of the conditionalTokens contract.
|
388
|
+
In comparison, the number of shares match the position.size from the user position.
|
389
|
+
"""
|
390
|
+
logger.info(f"Selling {amount=} from {outcome=}")
|
391
|
+
clob_manager = ClobManager(api_keys=api_keys or APIKeys())
|
392
|
+
token_id = self.get_token_id_for_outcome(outcome)
|
393
|
+
token_shares: OutcomeToken
|
394
|
+
if isinstance(amount, OutcomeToken):
|
395
|
+
token_shares = amount
|
396
|
+
elif isinstance(amount, USD):
|
397
|
+
token_price = clob_manager.get_token_price(
|
398
|
+
token_id=token_id, side=PolymarketPriceSideEnum.SELL
|
399
|
+
)
|
400
|
+
# We expect that our order sizes don't move the price too much.
|
401
|
+
token_shares = OutcomeToken(amount.value / token_price.value)
|
402
|
+
else:
|
403
|
+
raise ValueError(f"Unsupported amount type {type(amount)}")
|
404
|
+
|
405
|
+
created_order = clob_manager.place_sell_market_order(
|
406
|
+
token_id=token_id, token_shares=token_shares
|
407
|
+
)
|
408
|
+
if not created_order.success:
|
409
|
+
raise ValueError(f"Error creating order: {created_order}")
|
410
|
+
|
411
|
+
return created_order.transactionsHashes[0].to_0x_hex()
|
@@ -0,0 +1,35 @@
|
|
1
|
+
from web3 import Web3
|
2
|
+
|
3
|
+
from prediction_market_agent_tooling.config import APIKeys
|
4
|
+
from prediction_market_agent_tooling.gtypes import ChecksumAddress
|
5
|
+
from prediction_market_agent_tooling.tools.contract import (
|
6
|
+
ConditionalTokenContract,
|
7
|
+
ContractERC20BaseClass,
|
8
|
+
ContractOnPolygonChain,
|
9
|
+
)
|
10
|
+
|
11
|
+
|
12
|
+
class USDCeContract(ContractERC20BaseClass, ContractOnPolygonChain):
|
13
|
+
# USDC.e is used by Polymarket.
|
14
|
+
address: ChecksumAddress = Web3.to_checksum_address(
|
15
|
+
"0x2791Bca1f2de4661ED88A30C99A7a9449Aa84174"
|
16
|
+
)
|
17
|
+
|
18
|
+
|
19
|
+
class PolymarketConditionalTokenContract(
|
20
|
+
ConditionalTokenContract, ContractOnPolygonChain
|
21
|
+
):
|
22
|
+
address: ChecksumAddress = Web3.to_checksum_address(
|
23
|
+
"0x4D97DCd97eC945f40cF65F87097ACe5EA0476045"
|
24
|
+
)
|
25
|
+
|
26
|
+
def approve_if_not_approved(
|
27
|
+
self, api_keys: APIKeys, for_address: ChecksumAddress, web3: Web3 | None = None
|
28
|
+
) -> None:
|
29
|
+
is_approved = self.isApprovedForAll(
|
30
|
+
owner=api_keys.public_key, for_address=for_address, web3=web3
|
31
|
+
)
|
32
|
+
if not is_approved:
|
33
|
+
self.setApprovalForAll(
|
34
|
+
api_keys=api_keys, for_address=for_address, approve=True, web3=web3
|
35
|
+
)
|
@@ -30,8 +30,9 @@ class PolymarketSubgraphHandler(BaseSubgraphHandler):
|
|
30
30
|
def get_conditions(
|
31
31
|
self, condition_ids: list[HexBytes]
|
32
32
|
) -> list[ConditionSubgraphModel]:
|
33
|
-
where_stms = {"id_in": [i.
|
33
|
+
where_stms = {"id_in": [i.to_0x_hex() for i in condition_ids]}
|
34
34
|
conditions = self.conditions_subgraph.Query.conditions(
|
35
|
+
first=len(condition_ids),
|
35
36
|
where=where_stms,
|
36
37
|
)
|
37
38
|
|
@@ -160,7 +160,7 @@ class SeerMarket(BaseModel):
|
|
160
160
|
@property
|
161
161
|
def url(self) -> str:
|
162
162
|
chain_id = RPCConfig().chain_id
|
163
|
-
return urljoin(SEER_BASE_URL, f"markets/{chain_id}/{self.id.
|
163
|
+
return urljoin(SEER_BASE_URL, f"markets/{chain_id}/{self.id.to_0x_hex()}")
|
164
164
|
|
165
165
|
|
166
166
|
class SeerMarketWithQuestions(SeerMarket):
|
@@ -2,11 +2,15 @@ from cachetools import TTLCache, cached
|
|
2
2
|
from pydantic import BaseModel
|
3
3
|
from web3 import Web3
|
4
4
|
|
5
|
+
from prediction_market_agent_tooling.deploy.constants import (
|
6
|
+
INVALID_OUTCOME_LOWERCASE_IDENTIFIER,
|
7
|
+
)
|
5
8
|
from prediction_market_agent_tooling.gtypes import (
|
6
9
|
ChecksumAddress,
|
7
10
|
CollateralToken,
|
8
11
|
HexAddress,
|
9
12
|
OutcomeStr,
|
13
|
+
OutcomeToken,
|
10
14
|
Probability,
|
11
15
|
)
|
12
16
|
from prediction_market_agent_tooling.loggers import logger
|
@@ -46,7 +50,7 @@ class PriceManager:
|
|
46
50
|
price_diff_pct = abs(old_price - normalized_price) / max(old_price, 0.01)
|
47
51
|
if price_diff_pct > max_price_diff:
|
48
52
|
logger.info(
|
49
|
-
f"{price_diff_pct=} larger than {max_price_diff=} for seer market {self.seer_market.id.
|
53
|
+
f"{price_diff_pct=} larger than {max_price_diff=} for seer market {self.seer_market.id.to_0x_hex()} "
|
50
54
|
)
|
51
55
|
|
52
56
|
def get_price_for_token(self, token: ChecksumAddress) -> CollateralToken | None:
|
@@ -182,3 +186,67 @@ class PriceManager:
|
|
182
186
|
normalized_prices[outcome] = new_price
|
183
187
|
|
184
188
|
return normalized_prices
|
189
|
+
|
190
|
+
def build_initial_probs_from_pool(
|
191
|
+
self, model: SeerMarket, wrapped_tokens: list[ChecksumAddress]
|
192
|
+
) -> tuple[dict[OutcomeStr, Probability], dict[OutcomeStr, OutcomeToken]]:
|
193
|
+
"""
|
194
|
+
Builds a map of outcome to probability and outcome token pool.
|
195
|
+
"""
|
196
|
+
probability_map = {}
|
197
|
+
outcome_token_pool = {}
|
198
|
+
wrapped_tokens_with_supply = [
|
199
|
+
(
|
200
|
+
token,
|
201
|
+
SeerSubgraphHandler().get_pool_by_token(
|
202
|
+
token, model.collateral_token_contract_address_checksummed
|
203
|
+
),
|
204
|
+
)
|
205
|
+
for token in wrapped_tokens
|
206
|
+
]
|
207
|
+
wrapped_tokens_with_supply = [
|
208
|
+
(token, pool)
|
209
|
+
for token, pool in wrapped_tokens_with_supply
|
210
|
+
if pool is not None
|
211
|
+
]
|
212
|
+
|
213
|
+
for token, pool in wrapped_tokens_with_supply:
|
214
|
+
if pool is None or pool.token1.id is None or pool.token0.id is None:
|
215
|
+
continue
|
216
|
+
if HexBytes(token) == HexBytes(pool.token1.id):
|
217
|
+
outcome_token_pool[
|
218
|
+
OutcomeStr(model.outcomes[wrapped_tokens.index(token)])
|
219
|
+
] = (
|
220
|
+
OutcomeToken(pool.totalValueLockedToken0)
|
221
|
+
if pool.totalValueLockedToken0 is not None
|
222
|
+
else OutcomeToken(0)
|
223
|
+
)
|
224
|
+
probability_map[
|
225
|
+
OutcomeStr(model.outcomes[wrapped_tokens.index(token)])
|
226
|
+
] = Probability(pool.token0Price.value)
|
227
|
+
else:
|
228
|
+
outcome_token_pool[
|
229
|
+
OutcomeStr(model.outcomes[wrapped_tokens.index(token)])
|
230
|
+
] = (
|
231
|
+
OutcomeToken(pool.totalValueLockedToken1)
|
232
|
+
if pool.totalValueLockedToken1 is not None
|
233
|
+
else OutcomeToken(0)
|
234
|
+
)
|
235
|
+
probability_map[
|
236
|
+
OutcomeStr(model.outcomes[wrapped_tokens.index(token)])
|
237
|
+
] = Probability(pool.token1Price.value)
|
238
|
+
|
239
|
+
for outcome in model.outcomes:
|
240
|
+
if outcome not in outcome_token_pool:
|
241
|
+
outcome_token_pool[outcome] = OutcomeToken(0)
|
242
|
+
logger.warning(
|
243
|
+
f"Outcome {outcome} not found in outcome_token_pool for market {self.seer_market.url}."
|
244
|
+
)
|
245
|
+
if outcome not in probability_map:
|
246
|
+
if INVALID_OUTCOME_LOWERCASE_IDENTIFIER not in outcome.lower():
|
247
|
+
raise PriceCalculationError(
|
248
|
+
f"Couldn't get probability for {outcome} for market {self.seer_market.url}."
|
249
|
+
)
|
250
|
+
else:
|
251
|
+
probability_map[outcome] = Probability(0)
|
252
|
+
return probability_map, outcome_token_pool
|
@@ -28,7 +28,6 @@ from prediction_market_agent_tooling.markets.agent_market import (
|
|
28
28
|
FilterBy,
|
29
29
|
ParentMarket,
|
30
30
|
ProcessedMarket,
|
31
|
-
ProcessedTradedMarket,
|
32
31
|
QuestionType,
|
33
32
|
SortBy,
|
34
33
|
)
|
@@ -38,7 +37,10 @@ from prediction_market_agent_tooling.markets.data_models import (
|
|
38
37
|
Resolution,
|
39
38
|
)
|
40
39
|
from prediction_market_agent_tooling.markets.market_fees import MarketFees
|
41
|
-
from prediction_market_agent_tooling.markets.omen.omen import
|
40
|
+
from prediction_market_agent_tooling.markets.omen.omen import (
|
41
|
+
OmenAgentMarket,
|
42
|
+
send_keeping_token_to_eoa_xdai,
|
43
|
+
)
|
42
44
|
from prediction_market_agent_tooling.markets.omen.omen_constants import (
|
43
45
|
SDAI_CONTRACT_ADDRESS,
|
44
46
|
)
|
@@ -136,7 +138,7 @@ class SeerAgentMarket(AgentMarket):
|
|
136
138
|
|
137
139
|
def store_trades(
|
138
140
|
self,
|
139
|
-
traded_market:
|
141
|
+
traded_market: ProcessedMarket | None,
|
140
142
|
keys: APIKeys,
|
141
143
|
agent_name: str,
|
142
144
|
web3: Web3 | None = None,
|
@@ -262,10 +264,6 @@ class SeerAgentMarket(AgentMarket):
|
|
262
264
|
amounts_ot=amounts_ot,
|
263
265
|
)
|
264
266
|
|
265
|
-
@staticmethod
|
266
|
-
def get_user_id(api_keys: APIKeys) -> str:
|
267
|
-
return OmenAgentMarket.get_user_id(api_keys)
|
268
|
-
|
269
267
|
@staticmethod
|
270
268
|
def _filter_markets_contained_in_trades(
|
271
269
|
api_keys: APIKeys,
|
@@ -325,9 +323,9 @@ class SeerAgentMarket(AgentMarket):
|
|
325
323
|
amounts=market_balances[market.id],
|
326
324
|
)
|
327
325
|
gnosis_router.redeem_to_base(api_keys, params=params, web3=web3)
|
328
|
-
logger.info(f"Redeemed market {market.id.
|
326
|
+
logger.info(f"Redeemed market {market.id.to_0x_hex()}")
|
329
327
|
except Exception as e:
|
330
|
-
logger.error(f"Failed to redeem market {market.id.
|
328
|
+
logger.error(f"Failed to redeem market {market.id.to_0x_hex()}, {e}")
|
331
329
|
|
332
330
|
# GnosisRouter withdraws sDai into wxDAI/xDai on its own, so no auto-withdraw needed by us.
|
333
331
|
|
@@ -345,6 +343,17 @@ class SeerAgentMarket(AgentMarket):
|
|
345
343
|
|
346
344
|
return False
|
347
345
|
|
346
|
+
def ensure_min_native_balance(
|
347
|
+
self,
|
348
|
+
min_required_balance: xDai,
|
349
|
+
multiplier: float = 3.0,
|
350
|
+
) -> None:
|
351
|
+
send_keeping_token_to_eoa_xdai(
|
352
|
+
api_keys=APIKeys(),
|
353
|
+
min_required_balance=min_required_balance,
|
354
|
+
multiplier=multiplier,
|
355
|
+
)
|
356
|
+
|
348
357
|
@staticmethod
|
349
358
|
def verify_operational_balance(api_keys: APIKeys) -> bool:
|
350
359
|
return OmenAgentMarket.verify_operational_balance(api_keys=api_keys)
|
@@ -367,7 +376,7 @@ class SeerAgentMarket(AgentMarket):
|
|
367
376
|
raise ValueError("Seer categorical markets must have 1 question.")
|
368
377
|
|
369
378
|
question = model.questions[0]
|
370
|
-
outcome = model.outcomes[int(question.question.best_answer.
|
379
|
+
outcome = model.outcomes[int(question.question.best_answer.to_0x_hex(), 16)]
|
371
380
|
return Resolution(outcome=outcome, invalid=False)
|
372
381
|
|
373
382
|
@staticmethod
|
@@ -411,13 +420,17 @@ class SeerAgentMarket(AgentMarket):
|
|
411
420
|
must_have_prices: bool,
|
412
421
|
) -> t.Optional["SeerAgentMarket"]:
|
413
422
|
price_manager = PriceManager(seer_market=model, seer_subgraph=seer_subgraph)
|
414
|
-
|
415
|
-
probability_map = {}
|
423
|
+
wrapped_tokens = [Web3.to_checksum_address(i) for i in model.wrapped_tokens]
|
416
424
|
try:
|
417
|
-
|
425
|
+
(
|
426
|
+
probability_map,
|
427
|
+
outcome_token_pool,
|
428
|
+
) = price_manager.build_initial_probs_from_pool(
|
429
|
+
model=model, wrapped_tokens=wrapped_tokens
|
430
|
+
)
|
418
431
|
except PriceCalculationError as e:
|
419
432
|
logger.info(
|
420
|
-
f"Error when calculating probabilities for market {model.id.
|
433
|
+
f"Error when calculating probabilities for market {model.id.to_0x_hex()} - {e}"
|
421
434
|
)
|
422
435
|
if must_have_prices:
|
423
436
|
# Price calculation failed, so don't return the market
|
@@ -428,7 +441,7 @@ class SeerAgentMarket(AgentMarket):
|
|
428
441
|
parent = SeerAgentMarket.get_parent(model=model, seer_subgraph=seer_subgraph)
|
429
442
|
|
430
443
|
market = SeerAgentMarket(
|
431
|
-
id=model.id.
|
444
|
+
id=model.id.to_0x_hex(),
|
432
445
|
question=model.title,
|
433
446
|
creator=model.creator,
|
434
447
|
created_time=model.created_time,
|
@@ -437,9 +450,9 @@ class SeerAgentMarket(AgentMarket):
|
|
437
450
|
condition_id=model.condition_id,
|
438
451
|
url=model.url,
|
439
452
|
close_time=model.close_time,
|
440
|
-
wrapped_tokens=
|
453
|
+
wrapped_tokens=wrapped_tokens,
|
441
454
|
fees=MarketFees.get_zero_fees(),
|
442
|
-
outcome_token_pool=
|
455
|
+
outcome_token_pool=outcome_token_pool,
|
443
456
|
outcomes_supply=model.outcomes_supply,
|
444
457
|
resolution=resolution,
|
445
458
|
volume=None,
|
@@ -521,7 +534,9 @@ class SeerAgentMarket(AgentMarket):
|
|
521
534
|
)
|
522
535
|
|
523
536
|
token_balance = token_contract.balance_of_in_tokens(
|
524
|
-
for_address=Web3.to_checksum_address(
|
537
|
+
for_address=Web3.to_checksum_address(
|
538
|
+
HexAddress(HexStr(pool.id.to_0x_hex()))
|
539
|
+
),
|
525
540
|
web3=web3,
|
526
541
|
)
|
527
542
|
collateral_balance = p.get_amount_of_token_in_collateral(
|
@@ -602,7 +617,7 @@ class SeerAgentMarket(AgentMarket):
|
|
602
617
|
)
|
603
618
|
cow_tx_hash = trades[0].txHash
|
604
619
|
logger.info(f"TxHash is {cow_tx_hash=} for {order_metadata.uid.root=}.")
|
605
|
-
return cow_tx_hash.
|
620
|
+
return cow_tx_hash.to_0x_hex()
|
606
621
|
|
607
622
|
except (
|
608
623
|
UnexpectedResponseError,
|
@@ -635,7 +650,7 @@ class SeerAgentMarket(AgentMarket):
|
|
635
650
|
)
|
636
651
|
swap_pool_tx_hash = tx_receipt["transactionHash"]
|
637
652
|
logger.info(f"TxHash is {swap_pool_tx_hash=}.")
|
638
|
-
return swap_pool_tx_hash.
|
653
|
+
return swap_pool_tx_hash.to_0x_hex()
|
639
654
|
|
640
655
|
def place_bet(
|
641
656
|
self,
|