prediction-market-agent-tooling 0.65.5__py3-none-any.whl → 0.69.17.dev1149__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/abis/agentresultmapping.abi.json +192 -0
- prediction_market_agent_tooling/abis/erc1155.abi.json +352 -0
- prediction_market_agent_tooling/abis/processor.abi.json +16 -0
- prediction_market_agent_tooling/abis/swapr_quoter.abi.json +221 -0
- prediction_market_agent_tooling/abis/swapr_router.abi.json +634 -0
- prediction_market_agent_tooling/benchmark/benchmark.py +1 -1
- prediction_market_agent_tooling/benchmark/utils.py +13 -0
- prediction_market_agent_tooling/chains.py +1 -0
- prediction_market_agent_tooling/config.py +61 -2
- prediction_market_agent_tooling/data_download/langfuse_data_downloader.py +405 -0
- prediction_market_agent_tooling/deploy/agent.py +199 -67
- prediction_market_agent_tooling/deploy/agent_example.py +1 -1
- prediction_market_agent_tooling/deploy/betting_strategy.py +412 -68
- prediction_market_agent_tooling/deploy/constants.py +6 -0
- prediction_market_agent_tooling/gtypes.py +11 -1
- prediction_market_agent_tooling/jobs/jobs_models.py +2 -2
- prediction_market_agent_tooling/jobs/omen/omen_jobs.py +19 -20
- prediction_market_agent_tooling/loggers.py +9 -1
- prediction_market_agent_tooling/logprobs_parser.py +2 -1
- prediction_market_agent_tooling/markets/agent_market.py +106 -18
- prediction_market_agent_tooling/markets/blockchain_utils.py +37 -19
- prediction_market_agent_tooling/markets/data_models.py +120 -7
- prediction_market_agent_tooling/markets/manifold/data_models.py +5 -3
- prediction_market_agent_tooling/markets/manifold/manifold.py +21 -2
- prediction_market_agent_tooling/markets/manifold/utils.py +8 -2
- prediction_market_agent_tooling/markets/market_type.py +74 -0
- prediction_market_agent_tooling/markets/markets.py +7 -99
- prediction_market_agent_tooling/markets/metaculus/data_models.py +3 -3
- prediction_market_agent_tooling/markets/metaculus/metaculus.py +5 -8
- prediction_market_agent_tooling/markets/omen/cow_contracts.py +5 -1
- prediction_market_agent_tooling/markets/omen/data_models.py +63 -32
- prediction_market_agent_tooling/markets/omen/omen.py +112 -23
- prediction_market_agent_tooling/markets/omen/omen_constants.py +8 -0
- prediction_market_agent_tooling/markets/omen/omen_contracts.py +18 -203
- prediction_market_agent_tooling/markets/omen/omen_resolving.py +33 -13
- prediction_market_agent_tooling/markets/omen/omen_subgraph_handler.py +23 -18
- prediction_market_agent_tooling/markets/polymarket/api.py +123 -100
- 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 +95 -19
- prediction_market_agent_tooling/markets/polymarket/polymarket.py +373 -29
- prediction_market_agent_tooling/markets/polymarket/polymarket_contracts.py +35 -0
- prediction_market_agent_tooling/markets/polymarket/polymarket_subgraph_handler.py +91 -0
- prediction_market_agent_tooling/markets/polymarket/utils.py +1 -22
- prediction_market_agent_tooling/markets/seer/data_models.py +111 -17
- prediction_market_agent_tooling/markets/seer/exceptions.py +2 -0
- prediction_market_agent_tooling/markets/seer/price_manager.py +165 -50
- prediction_market_agent_tooling/markets/seer/seer.py +393 -106
- prediction_market_agent_tooling/markets/seer/seer_api.py +28 -0
- prediction_market_agent_tooling/markets/seer/seer_contracts.py +115 -5
- prediction_market_agent_tooling/markets/seer/seer_subgraph_handler.py +297 -66
- prediction_market_agent_tooling/markets/seer/subgraph_data_models.py +43 -8
- prediction_market_agent_tooling/markets/seer/swap_pool_handler.py +80 -0
- prediction_market_agent_tooling/tools/_generic_value.py +8 -2
- prediction_market_agent_tooling/tools/betting_strategies/kelly_criterion.py +271 -8
- prediction_market_agent_tooling/tools/betting_strategies/utils.py +6 -1
- prediction_market_agent_tooling/tools/caches/db_cache.py +219 -117
- prediction_market_agent_tooling/tools/caches/serializers.py +11 -2
- prediction_market_agent_tooling/tools/contract.py +480 -38
- prediction_market_agent_tooling/tools/contract_utils.py +61 -0
- prediction_market_agent_tooling/tools/cow/cow_order.py +218 -45
- prediction_market_agent_tooling/tools/cow/models.py +122 -0
- prediction_market_agent_tooling/tools/cow/semaphore.py +104 -0
- prediction_market_agent_tooling/tools/datetime_utc.py +14 -2
- prediction_market_agent_tooling/tools/db/db_manager.py +59 -0
- prediction_market_agent_tooling/tools/hexbytes_custom.py +4 -1
- prediction_market_agent_tooling/tools/httpx_cached_client.py +15 -6
- prediction_market_agent_tooling/tools/langfuse_client_utils.py +21 -8
- prediction_market_agent_tooling/tools/openai_utils.py +31 -0
- prediction_market_agent_tooling/tools/perplexity/perplexity_client.py +86 -0
- prediction_market_agent_tooling/tools/perplexity/perplexity_models.py +26 -0
- prediction_market_agent_tooling/tools/perplexity/perplexity_search.py +73 -0
- prediction_market_agent_tooling/tools/rephrase.py +71 -0
- prediction_market_agent_tooling/tools/singleton.py +11 -6
- prediction_market_agent_tooling/tools/streamlit_utils.py +188 -0
- prediction_market_agent_tooling/tools/tokens/auto_deposit.py +64 -0
- prediction_market_agent_tooling/tools/tokens/auto_withdraw.py +8 -0
- prediction_market_agent_tooling/tools/tokens/slippage.py +21 -0
- prediction_market_agent_tooling/tools/tokens/usd.py +5 -2
- prediction_market_agent_tooling/tools/utils.py +61 -3
- prediction_market_agent_tooling/tools/web3_utils.py +63 -9
- {prediction_market_agent_tooling-0.65.5.dist-info → prediction_market_agent_tooling-0.69.17.dev1149.dist-info}/METADATA +13 -9
- {prediction_market_agent_tooling-0.65.5.dist-info → prediction_market_agent_tooling-0.69.17.dev1149.dist-info}/RECORD +86 -64
- {prediction_market_agent_tooling-0.65.5.dist-info → prediction_market_agent_tooling-0.69.17.dev1149.dist-info}/WHEEL +1 -1
- prediction_market_agent_tooling/abis/omen_agentresultmapping.abi.json +0 -171
- prediction_market_agent_tooling/markets/polymarket/data_models_web.py +0 -420
- {prediction_market_agent_tooling-0.65.5.dist-info → prediction_market_agent_tooling-0.69.17.dev1149.dist-info}/entry_points.txt +0 -0
- {prediction_market_agent_tooling-0.65.5.dist-info → prediction_market_agent_tooling-0.69.17.dev1149.dist-info/licenses}/LICENSE +0 -0
|
@@ -1,22 +1,65 @@
|
|
|
1
1
|
import typing as t
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
import cachetools
|
|
4
|
+
from web3 import Web3
|
|
5
|
+
|
|
6
|
+
from prediction_market_agent_tooling.config import APIKeys, RPCConfig
|
|
7
|
+
from prediction_market_agent_tooling.gtypes import (
|
|
8
|
+
USD,
|
|
9
|
+
ChecksumAddress,
|
|
10
|
+
CollateralToken,
|
|
11
|
+
HexBytes,
|
|
12
|
+
OutcomeStr,
|
|
13
|
+
OutcomeToken,
|
|
14
|
+
Probability,
|
|
15
|
+
Wei,
|
|
16
|
+
xDai,
|
|
17
|
+
)
|
|
18
|
+
from prediction_market_agent_tooling.loggers import logger
|
|
4
19
|
from prediction_market_agent_tooling.markets.agent_market import (
|
|
5
20
|
AgentMarket,
|
|
21
|
+
ConditionalFilterType,
|
|
6
22
|
FilterBy,
|
|
7
23
|
MarketFees,
|
|
24
|
+
ProcessedMarket,
|
|
25
|
+
QuestionType,
|
|
8
26
|
SortBy,
|
|
9
27
|
)
|
|
28
|
+
from prediction_market_agent_tooling.markets.data_models import (
|
|
29
|
+
ExistingPosition,
|
|
30
|
+
Resolution,
|
|
31
|
+
)
|
|
10
32
|
from prediction_market_agent_tooling.markets.polymarket.api import (
|
|
11
|
-
|
|
33
|
+
PolymarketOrderByEnum,
|
|
34
|
+
get_polymarkets_with_pagination,
|
|
35
|
+
get_user_positions,
|
|
12
36
|
)
|
|
13
|
-
from prediction_market_agent_tooling.markets.polymarket.
|
|
14
|
-
|
|
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,
|
|
15
43
|
)
|
|
16
|
-
from prediction_market_agent_tooling.markets.polymarket.
|
|
44
|
+
from prediction_market_agent_tooling.markets.polymarket.data_models import (
|
|
17
45
|
POLYMARKET_BASE_URL,
|
|
46
|
+
PolymarketGammaResponseDataItem,
|
|
47
|
+
)
|
|
48
|
+
from prediction_market_agent_tooling.markets.polymarket.polymarket_contracts import (
|
|
49
|
+
PolymarketConditionalTokenContract,
|
|
50
|
+
USDCeContract,
|
|
51
|
+
)
|
|
52
|
+
from prediction_market_agent_tooling.markets.polymarket.polymarket_subgraph_handler import (
|
|
53
|
+
ConditionSubgraphModel,
|
|
54
|
+
PolymarketSubgraphHandler,
|
|
55
|
+
)
|
|
56
|
+
from prediction_market_agent_tooling.tools.datetime_utc import DatetimeUTC
|
|
57
|
+
from prediction_market_agent_tooling.tools.tokens.usd import get_token_in_usd
|
|
58
|
+
from prediction_market_agent_tooling.tools.utils import check_not_none
|
|
59
|
+
|
|
60
|
+
SHARED_CACHE: cachetools.TTLCache[t.Hashable, t.Any] = cachetools.TTLCache(
|
|
61
|
+
maxsize=256, ttl=10 * 60
|
|
18
62
|
)
|
|
19
|
-
from prediction_market_agent_tooling.tools.utils import DatetimeUTC
|
|
20
63
|
|
|
21
64
|
|
|
22
65
|
class PolymarketAgentMarket(AgentMarket):
|
|
@@ -31,25 +74,111 @@ class PolymarketAgentMarket(AgentMarket):
|
|
|
31
74
|
# 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)`.
|
|
32
75
|
# TODO: Check out the fees while integrating the subgraph API or if we implement placing of bets on Polymarket.
|
|
33
76
|
fees: MarketFees = MarketFees.get_zero_fees()
|
|
77
|
+
condition_id: HexBytes
|
|
78
|
+
liquidity_usd: USD
|
|
79
|
+
token_ids: list[int]
|
|
80
|
+
closed_flag_from_polymarket: bool
|
|
81
|
+
active_flag_from_polymarket: bool
|
|
34
82
|
|
|
35
83
|
@staticmethod
|
|
36
|
-
def
|
|
84
|
+
def collateral_token_address() -> ChecksumAddress:
|
|
85
|
+
return USDCeContract().address
|
|
86
|
+
|
|
87
|
+
@staticmethod
|
|
88
|
+
def build_resolution_from_condition(
|
|
89
|
+
condition_id: HexBytes,
|
|
90
|
+
condition_model_dict: dict[HexBytes, ConditionSubgraphModel],
|
|
91
|
+
outcomes: list[OutcomeStr],
|
|
92
|
+
) -> Resolution | None:
|
|
93
|
+
condition_model = condition_model_dict.get(condition_id)
|
|
94
|
+
if (
|
|
95
|
+
not condition_model
|
|
96
|
+
or condition_model.resolutionTimestamp is None
|
|
97
|
+
or not condition_model.payoutNumerators
|
|
98
|
+
or not condition_model.payoutDenominator
|
|
99
|
+
):
|
|
100
|
+
return None
|
|
101
|
+
|
|
102
|
+
# Currently we only support binary markets, hence we throw an error if we get something else.
|
|
103
|
+
payout_numerator_indices_gt_0 = [
|
|
104
|
+
idx
|
|
105
|
+
for idx, value in enumerate(condition_model.payoutNumerators)
|
|
106
|
+
if value > 0
|
|
107
|
+
]
|
|
108
|
+
# For a binary market, there should be exactly one payout numerator greater than 0.
|
|
109
|
+
if len(payout_numerator_indices_gt_0) != 1:
|
|
110
|
+
# These cases involve multi-categorical resolution (to be implemented https://github.com/gnosis/prediction-market-agent-tooling/issues/770)
|
|
111
|
+
logger.warning(
|
|
112
|
+
f"Only binary markets are supported. Got payout numerators: {condition_model.payoutNumerators} for condition_id {condition_id.to_0x_hex()}"
|
|
113
|
+
)
|
|
114
|
+
return Resolution(outcome=None, invalid=False)
|
|
115
|
+
|
|
116
|
+
# we return the only payout numerator greater than 0 as resolution
|
|
117
|
+
resolved_outcome = outcomes[payout_numerator_indices_gt_0[0]]
|
|
118
|
+
return Resolution.from_answer(resolved_outcome)
|
|
119
|
+
|
|
120
|
+
def get_token_id_for_outcome(self, outcome: OutcomeStr) -> int:
|
|
121
|
+
outcome_idx = self.outcomes.index(outcome)
|
|
122
|
+
return self.token_ids[outcome_idx]
|
|
123
|
+
|
|
124
|
+
@staticmethod
|
|
125
|
+
def from_data_model(
|
|
126
|
+
model: PolymarketGammaResponseDataItem,
|
|
127
|
+
condition_model_dict: dict[HexBytes, ConditionSubgraphModel],
|
|
128
|
+
) -> t.Optional["PolymarketAgentMarket"]:
|
|
129
|
+
# If len(model.markets) > 0, this denotes a categorical market.
|
|
130
|
+
markets = check_not_none(model.markets)
|
|
131
|
+
outcomes = markets[0].outcomes_list
|
|
132
|
+
outcome_prices = markets[0].outcome_prices
|
|
133
|
+
if not outcome_prices:
|
|
134
|
+
logger.info(f"Market has no outcome prices. Skipping. {model=}")
|
|
135
|
+
return None
|
|
136
|
+
|
|
137
|
+
probabilities = {o: Probability(op) for o, op in zip(outcomes, outcome_prices)}
|
|
138
|
+
|
|
139
|
+
condition_id = markets[0].conditionId
|
|
140
|
+
resolution = PolymarketAgentMarket.build_resolution_from_condition(
|
|
141
|
+
condition_id=condition_id,
|
|
142
|
+
condition_model_dict=condition_model_dict,
|
|
143
|
+
outcomes=outcomes,
|
|
144
|
+
)
|
|
145
|
+
|
|
37
146
|
return PolymarketAgentMarket(
|
|
38
147
|
id=model.id,
|
|
39
|
-
|
|
148
|
+
condition_id=condition_id,
|
|
149
|
+
question=model.title,
|
|
40
150
|
description=model.description,
|
|
41
|
-
outcomes=
|
|
42
|
-
resolution=
|
|
43
|
-
created_time=
|
|
44
|
-
close_time=model.
|
|
151
|
+
outcomes=outcomes,
|
|
152
|
+
resolution=resolution,
|
|
153
|
+
created_time=model.startDate,
|
|
154
|
+
close_time=model.endDate,
|
|
155
|
+
closed_flag_from_polymarket=model.closed,
|
|
156
|
+
active_flag_from_polymarket=model.active,
|
|
45
157
|
url=model.url,
|
|
46
|
-
volume=None,
|
|
158
|
+
volume=CollateralToken(model.volume) if model.volume else None,
|
|
47
159
|
outcome_token_pool=None,
|
|
48
|
-
probabilities=
|
|
160
|
+
probabilities=probabilities,
|
|
161
|
+
liquidity_usd=USD(model.liquidity)
|
|
162
|
+
if model.liquidity is not None
|
|
163
|
+
else USD(0),
|
|
164
|
+
token_ids=markets[0].token_ids,
|
|
49
165
|
)
|
|
50
166
|
|
|
51
167
|
def get_tiny_bet_amount(self) -> CollateralToken:
|
|
52
|
-
|
|
168
|
+
return CollateralToken(POLYMARKET_TINY_BET_AMOUNT.value)
|
|
169
|
+
|
|
170
|
+
def get_token_in_usd(self, x: CollateralToken) -> USD:
|
|
171
|
+
return get_token_in_usd(x, self.collateral_token_address())
|
|
172
|
+
|
|
173
|
+
@staticmethod
|
|
174
|
+
def get_trade_balance(api_keys: APIKeys, web3: Web3 | None = None) -> USD:
|
|
175
|
+
usdc_balance_wei = USDCeContract().balanceOf(
|
|
176
|
+
for_address=api_keys.public_key, web3=web3
|
|
177
|
+
)
|
|
178
|
+
return USD(usdc_balance_wei.value * 1e-6)
|
|
179
|
+
|
|
180
|
+
def get_liquidity(self, web3: Web3 | None = None) -> CollateralToken:
|
|
181
|
+
return CollateralToken(self.liquidity_usd.value)
|
|
53
182
|
|
|
54
183
|
def place_bet(self, outcome: OutcomeStr, amount: USD) -> str:
|
|
55
184
|
raise NotImplementedError("TODO: Implement to allow betting on Polymarket.")
|
|
@@ -61,15 +190,11 @@ class PolymarketAgentMarket(AgentMarket):
|
|
|
61
190
|
filter_by: FilterBy = FilterBy.OPEN,
|
|
62
191
|
created_after: t.Optional[DatetimeUTC] = None,
|
|
63
192
|
excluded_questions: set[str] | None = None,
|
|
64
|
-
|
|
193
|
+
question_type: QuestionType = QuestionType.ALL,
|
|
194
|
+
conditional_filter_type: ConditionalFilterType = ConditionalFilterType.ONLY_NOT_CONDITIONAL,
|
|
65
195
|
) -> t.Sequence["PolymarketAgentMarket"]:
|
|
66
|
-
if sort_by != SortBy.NONE:
|
|
67
|
-
raise ValueError(f"Unsuported sort_by {sort_by} for Polymarket.")
|
|
68
|
-
|
|
69
|
-
if created_after is not None:
|
|
70
|
-
raise ValueError(f"Unsuported created_after for Polymarket.")
|
|
71
|
-
|
|
72
196
|
closed: bool | None
|
|
197
|
+
|
|
73
198
|
if filter_by == FilterBy.OPEN:
|
|
74
199
|
closed = False
|
|
75
200
|
elif filter_by == FilterBy.RESOLVED:
|
|
@@ -79,11 +204,230 @@ class PolymarketAgentMarket(AgentMarket):
|
|
|
79
204
|
else:
|
|
80
205
|
raise ValueError(f"Unknown filter_by: {filter_by}")
|
|
81
206
|
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
207
|
+
ascending: bool = False # default value
|
|
208
|
+
match sort_by:
|
|
209
|
+
case SortBy.NEWEST:
|
|
210
|
+
order_by = PolymarketOrderByEnum.START_DATE
|
|
211
|
+
ascending = False
|
|
212
|
+
case SortBy.CLOSING_SOONEST:
|
|
213
|
+
ascending = True
|
|
214
|
+
order_by = PolymarketOrderByEnum.END_DATE
|
|
215
|
+
case SortBy.HIGHEST_LIQUIDITY:
|
|
216
|
+
order_by = PolymarketOrderByEnum.LIQUIDITY
|
|
217
|
+
case SortBy.NONE:
|
|
218
|
+
order_by = PolymarketOrderByEnum.VOLUME_24HR
|
|
219
|
+
case _:
|
|
220
|
+
raise ValueError(f"Unknown sort_by: {sort_by}")
|
|
221
|
+
|
|
222
|
+
# closed markets also have property active=True, hence ignoring active.
|
|
223
|
+
markets = get_polymarkets_with_pagination(
|
|
224
|
+
limit=limit,
|
|
225
|
+
closed=closed,
|
|
226
|
+
order_by=order_by,
|
|
227
|
+
ascending=ascending,
|
|
228
|
+
created_after=created_after,
|
|
229
|
+
excluded_questions=excluded_questions,
|
|
230
|
+
only_binary=question_type is not QuestionType.CATEGORICAL,
|
|
231
|
+
)
|
|
232
|
+
|
|
233
|
+
condition_models = PolymarketSubgraphHandler().get_conditions(
|
|
234
|
+
condition_ids=list(
|
|
235
|
+
set(
|
|
236
|
+
[
|
|
237
|
+
market.markets[0].conditionId
|
|
238
|
+
for market in markets
|
|
239
|
+
if market.markets is not None
|
|
240
|
+
]
|
|
241
|
+
)
|
|
88
242
|
)
|
|
89
|
-
|
|
243
|
+
)
|
|
244
|
+
condition_models_dict = {c.id: c for c in condition_models}
|
|
245
|
+
|
|
246
|
+
result_markets: list[PolymarketAgentMarket] = []
|
|
247
|
+
for m in markets:
|
|
248
|
+
market = PolymarketAgentMarket.from_data_model(m, condition_models_dict)
|
|
249
|
+
if market is not None:
|
|
250
|
+
result_markets.append(market)
|
|
251
|
+
return result_markets
|
|
252
|
+
|
|
253
|
+
def ensure_min_native_balance(
|
|
254
|
+
self,
|
|
255
|
+
min_required_balance: xDai,
|
|
256
|
+
multiplier: float = 3.0,
|
|
257
|
+
web3: Web3 | None = None,
|
|
258
|
+
) -> None:
|
|
259
|
+
balance_collateral = USDCeContract().balanceOf(
|
|
260
|
+
for_address=APIKeys().public_key, web3=web3
|
|
261
|
+
)
|
|
262
|
+
# USDC has 6 decimals, xDAI has 18. We convert from Wei into atomic units.
|
|
263
|
+
balance_collateral_atomic = CollateralToken(float(balance_collateral) / 1e6)
|
|
264
|
+
if balance_collateral_atomic < min_required_balance.as_token:
|
|
265
|
+
raise EnvironmentError(
|
|
266
|
+
f"USDC balance {balance_collateral_atomic} < {min_required_balance.as_token=}"
|
|
267
|
+
)
|
|
268
|
+
|
|
269
|
+
@staticmethod
|
|
270
|
+
def redeem_winnings(api_keys: APIKeys, web3: Web3 | None = None) -> None:
|
|
271
|
+
web3 = web3 or RPCConfig().get_polygon_web3()
|
|
272
|
+
user_id = api_keys.bet_from_address
|
|
273
|
+
conditional_token_contract = PolymarketConditionalTokenContract()
|
|
274
|
+
positions = PolymarketSubgraphHandler().get_market_positions_from_user(user_id)
|
|
275
|
+
for pos in positions:
|
|
276
|
+
if (
|
|
277
|
+
pos.market.condition.resolutionTimestamp is None
|
|
278
|
+
or pos.market.condition.payoutNumerators is None
|
|
279
|
+
):
|
|
280
|
+
continue
|
|
281
|
+
|
|
282
|
+
condition_id = pos.market.condition.id
|
|
283
|
+
index_sets = pos.market.condition.index_sets
|
|
284
|
+
|
|
285
|
+
redeem_event = conditional_token_contract.redeemPositions(
|
|
286
|
+
api_keys=api_keys,
|
|
287
|
+
collateral_token_address=USDCeContract().address,
|
|
288
|
+
condition_id=condition_id,
|
|
289
|
+
index_sets=index_sets,
|
|
290
|
+
web3=web3,
|
|
291
|
+
)
|
|
292
|
+
|
|
293
|
+
logger.info(f"Redeemed {redeem_event=} from condition_id {condition_id=}.")
|
|
294
|
+
|
|
295
|
+
@staticmethod
|
|
296
|
+
def verify_operational_balance(api_keys: APIKeys) -> bool:
|
|
297
|
+
"""Method for checking if agent has enough funds to pay for gas fees."""
|
|
298
|
+
web3 = RPCConfig().get_polygon_web3()
|
|
299
|
+
pol_balance: Wei = Wei(web3.eth.get_balance(api_keys.public_key))
|
|
300
|
+
return pol_balance > Wei(int(0.001 * 1e18))
|
|
301
|
+
|
|
302
|
+
def store_prediction(
|
|
303
|
+
self,
|
|
304
|
+
processed_market: ProcessedMarket | None,
|
|
305
|
+
keys: APIKeys,
|
|
306
|
+
agent_name: str,
|
|
307
|
+
) -> None:
|
|
308
|
+
pass
|
|
309
|
+
|
|
310
|
+
def store_trades(
|
|
311
|
+
self,
|
|
312
|
+
traded_market: ProcessedMarket | None,
|
|
313
|
+
keys: APIKeys,
|
|
314
|
+
agent_name: str,
|
|
315
|
+
web3: Web3 | None = None,
|
|
316
|
+
) -> None:
|
|
317
|
+
logger.info("Storing trades deactivated for Polymarket.")
|
|
318
|
+
# Understand how market_id can be represented.
|
|
319
|
+
# Condition_id could work but length doesn't seem to match.
|
|
320
|
+
|
|
321
|
+
@classmethod
|
|
322
|
+
def get_user_url(cls, keys: APIKeys) -> str:
|
|
323
|
+
return f"https://polymarket.com/{keys.public_key}"
|
|
324
|
+
|
|
325
|
+
def get_position(
|
|
326
|
+
self, user_id: str, web3: Web3 | None = None
|
|
327
|
+
) -> ExistingPosition | None:
|
|
328
|
+
"""
|
|
329
|
+
Fetches position from the user in a given market.
|
|
330
|
+
"""
|
|
331
|
+
positions = get_user_positions(
|
|
332
|
+
user_id=Web3.to_checksum_address(user_id), condition_ids=[self.condition_id]
|
|
333
|
+
)
|
|
334
|
+
if not positions:
|
|
335
|
+
return None
|
|
336
|
+
|
|
337
|
+
amounts_ot = {i: OutcomeToken(0) for i in self.outcomes}
|
|
338
|
+
amounts_potential = {i: USD(0) for i in self.outcomes}
|
|
339
|
+
amounts_current = {i: USD(0) for i in self.outcomes}
|
|
340
|
+
|
|
341
|
+
for p in positions:
|
|
342
|
+
if p.conditionId != self.condition_id.to_0x_hex():
|
|
343
|
+
continue
|
|
344
|
+
|
|
345
|
+
amounts_potential[OutcomeStr(p.outcome)] = USD(p.size)
|
|
346
|
+
amounts_ot[OutcomeStr(p.outcome)] = OutcomeToken(p.size)
|
|
347
|
+
amounts_current[OutcomeStr(p.outcome)] = USD(p.currentValue)
|
|
348
|
+
|
|
349
|
+
return ExistingPosition(
|
|
350
|
+
amounts_potential=amounts_potential,
|
|
351
|
+
amounts_ot=amounts_ot,
|
|
352
|
+
market_id=self.id,
|
|
353
|
+
amounts_current=amounts_current,
|
|
354
|
+
)
|
|
355
|
+
|
|
356
|
+
def can_be_traded(self) -> bool:
|
|
357
|
+
return (
|
|
358
|
+
self.active_flag_from_polymarket
|
|
359
|
+
and not self.closed_flag_from_polymarket
|
|
360
|
+
and self.liquidity_usd
|
|
361
|
+
> USD(5) # we conservatively require some positive liquidity to trade on
|
|
362
|
+
)
|
|
363
|
+
|
|
364
|
+
def get_buy_token_amount(
|
|
365
|
+
self, bet_amount: USD | CollateralToken, outcome_str: OutcomeStr
|
|
366
|
+
) -> OutcomeToken:
|
|
367
|
+
"""Returns number of outcome tokens returned for a given bet expressed in collateral units."""
|
|
368
|
+
|
|
369
|
+
if outcome_str not in self.outcomes:
|
|
370
|
+
raise ValueError(
|
|
371
|
+
f"Outcome {outcome_str} not found in market outcomes {self.outcomes}"
|
|
372
|
+
)
|
|
373
|
+
|
|
374
|
+
token_id = self.get_token_id_for_outcome(outcome_str)
|
|
375
|
+
|
|
376
|
+
price = ClobManager(APIKeys()).get_token_price(
|
|
377
|
+
token_id=token_id, side=PolymarketPriceSideEnum.BUY
|
|
378
|
+
)
|
|
379
|
+
if not price:
|
|
380
|
+
raise ValueError(
|
|
381
|
+
f"Could not get price for outcome {outcome_str} with token_id {token_id}"
|
|
382
|
+
)
|
|
383
|
+
|
|
384
|
+
# we work with floats since USD and Collateral are the same on Polymarket
|
|
385
|
+
buy_token_amount = bet_amount.value / price.value
|
|
386
|
+
logger.info(f"Buy token amount: {buy_token_amount=}")
|
|
387
|
+
return OutcomeToken(buy_token_amount)
|
|
388
|
+
|
|
389
|
+
def buy_tokens(self, outcome: OutcomeStr, amount: USD) -> str:
|
|
390
|
+
clob_manager = ClobManager(APIKeys())
|
|
391
|
+
token_id = self.get_token_id_for_outcome(outcome)
|
|
392
|
+
|
|
393
|
+
created_order = clob_manager.place_buy_market_order(
|
|
394
|
+
token_id=token_id, usdc_amount=amount
|
|
395
|
+
)
|
|
396
|
+
if not created_order.success:
|
|
397
|
+
raise ValueError(f"Error creating order: {created_order}")
|
|
398
|
+
|
|
399
|
+
return created_order.transactionsHashes[0].to_0x_hex()
|
|
400
|
+
|
|
401
|
+
def sell_tokens(
|
|
402
|
+
self,
|
|
403
|
+
outcome: OutcomeStr,
|
|
404
|
+
amount: USD | OutcomeToken,
|
|
405
|
+
api_keys: APIKeys | None = None,
|
|
406
|
+
) -> str:
|
|
407
|
+
"""
|
|
408
|
+
Polymarket's API expect shares to be sold. 1 share == 1 outcome token / 1e6.
|
|
409
|
+
The number of outcome tokens matches the `balanceOf` of the conditionalTokens contract.
|
|
410
|
+
In comparison, the number of shares match the position.size from the user position.
|
|
411
|
+
"""
|
|
412
|
+
logger.info(f"Selling {amount=} from {outcome=}")
|
|
413
|
+
clob_manager = ClobManager(api_keys=api_keys or APIKeys())
|
|
414
|
+
token_id = self.get_token_id_for_outcome(outcome)
|
|
415
|
+
token_shares: OutcomeToken
|
|
416
|
+
if isinstance(amount, OutcomeToken):
|
|
417
|
+
token_shares = amount
|
|
418
|
+
elif isinstance(amount, USD):
|
|
419
|
+
token_price = clob_manager.get_token_price(
|
|
420
|
+
token_id=token_id, side=PolymarketPriceSideEnum.SELL
|
|
421
|
+
)
|
|
422
|
+
# We expect that our order sizes don't move the price too much.
|
|
423
|
+
token_shares = OutcomeToken(amount.value / token_price.value)
|
|
424
|
+
else:
|
|
425
|
+
raise ValueError(f"Unsupported amount type {type(amount)}")
|
|
426
|
+
|
|
427
|
+
created_order = clob_manager.place_sell_market_order(
|
|
428
|
+
token_id=token_id, token_shares=token_shares
|
|
429
|
+
)
|
|
430
|
+
if not created_order.success:
|
|
431
|
+
raise ValueError(f"Error creating order: {created_order}")
|
|
432
|
+
|
|
433
|
+
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
|
+
)
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
from pydantic import BaseModel
|
|
2
|
+
from subgrounds import FieldPath
|
|
3
|
+
|
|
4
|
+
from prediction_market_agent_tooling.gtypes import ChecksumAddress, HexBytes
|
|
5
|
+
from prediction_market_agent_tooling.markets.base_subgraph_handler import (
|
|
6
|
+
BaseSubgraphHandler,
|
|
7
|
+
)
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class ConditionSubgraphModel(BaseModel):
|
|
11
|
+
id: HexBytes
|
|
12
|
+
payoutDenominator: int | None = None
|
|
13
|
+
payoutNumerators: list[int] | None = None
|
|
14
|
+
outcomeSlotCount: int
|
|
15
|
+
resolutionTimestamp: int | None = None
|
|
16
|
+
questionId: HexBytes
|
|
17
|
+
|
|
18
|
+
@property
|
|
19
|
+
def index_sets(self) -> list[int]:
|
|
20
|
+
return [i + 1 for i in range(self.outcomeSlotCount)]
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class MarketPositionMarket(BaseModel):
|
|
24
|
+
condition: ConditionSubgraphModel
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class MarketPosition(BaseModel):
|
|
28
|
+
id: HexBytes
|
|
29
|
+
market: MarketPositionMarket
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
class PolymarketSubgraphHandler(BaseSubgraphHandler):
|
|
33
|
+
POLYMARKET_CONDITIONS_SUBGRAPH = "https://gateway.thegraph.com/api/{graph_api_key}/subgraphs/id/81Dm16JjuFSrqz813HysXoUPvzTwE7fsfPk2RTf66nyC"
|
|
34
|
+
|
|
35
|
+
def __init__(self) -> None:
|
|
36
|
+
super().__init__()
|
|
37
|
+
|
|
38
|
+
# Load the subgraph
|
|
39
|
+
self.conditions_subgraph = self.sg.load_subgraph(
|
|
40
|
+
self.POLYMARKET_CONDITIONS_SUBGRAPH.format(
|
|
41
|
+
graph_api_key=self.keys.graph_api_key.get_secret_value()
|
|
42
|
+
)
|
|
43
|
+
)
|
|
44
|
+
|
|
45
|
+
def _get_fields_for_condition(self, field: FieldPath) -> list[FieldPath]:
|
|
46
|
+
return [
|
|
47
|
+
field.id,
|
|
48
|
+
field.questionId,
|
|
49
|
+
field.payoutNumerators,
|
|
50
|
+
field.payoutDenominator,
|
|
51
|
+
field.outcomeSlotCount,
|
|
52
|
+
field.resolutionTimestamp,
|
|
53
|
+
]
|
|
54
|
+
|
|
55
|
+
def get_conditions(
|
|
56
|
+
self, condition_ids: list[HexBytes]
|
|
57
|
+
) -> list[ConditionSubgraphModel]:
|
|
58
|
+
where_stms = {"id_in": [i.to_0x_hex() for i in condition_ids]}
|
|
59
|
+
conditions = self.conditions_subgraph.Query.conditions(
|
|
60
|
+
first=len(condition_ids),
|
|
61
|
+
where=where_stms,
|
|
62
|
+
)
|
|
63
|
+
|
|
64
|
+
condition_fields = self._get_fields_for_condition(conditions)
|
|
65
|
+
|
|
66
|
+
conditions_models = self.do_query(
|
|
67
|
+
fields=condition_fields, pydantic_model=ConditionSubgraphModel
|
|
68
|
+
)
|
|
69
|
+
return conditions_models
|
|
70
|
+
|
|
71
|
+
def get_market_positions_from_user(
|
|
72
|
+
self,
|
|
73
|
+
user: ChecksumAddress,
|
|
74
|
+
first: int = 1000,
|
|
75
|
+
block_number: int | None = None,
|
|
76
|
+
) -> list[MarketPosition]:
|
|
77
|
+
# Not possible to filter using `market_.condition` on a subgraph level, bad indexers error.
|
|
78
|
+
positions = self.conditions_subgraph.Query.marketPositions(
|
|
79
|
+
first=first,
|
|
80
|
+
where={"user": user.lower()},
|
|
81
|
+
block={"number": block_number} if block_number else None,
|
|
82
|
+
)
|
|
83
|
+
|
|
84
|
+
condition_fields = self._get_fields_for_condition(
|
|
85
|
+
positions.market.condition
|
|
86
|
+
) + [positions.id]
|
|
87
|
+
|
|
88
|
+
positions_models = self.do_query(
|
|
89
|
+
fields=condition_fields, pydantic_model=MarketPosition
|
|
90
|
+
)
|
|
91
|
+
return positions_models
|
|
@@ -1,28 +1,7 @@
|
|
|
1
|
-
from prediction_market_agent_tooling.markets.
|
|
2
|
-
from prediction_market_agent_tooling.markets.markets import MarketType
|
|
3
|
-
from prediction_market_agent_tooling.markets.polymarket.data_models_web import (
|
|
4
|
-
PolymarketFullMarket,
|
|
5
|
-
)
|
|
1
|
+
from prediction_market_agent_tooling.markets.market_type import MarketType
|
|
6
2
|
from prediction_market_agent_tooling.tools.google_utils import search_google_gcp
|
|
7
3
|
|
|
8
4
|
|
|
9
|
-
def find_resolution_on_polymarket(question: str) -> Resolution | None:
|
|
10
|
-
full_market = find_full_polymarket(question)
|
|
11
|
-
# TODO: Only main markets are supported right now, add logic for others if needed.
|
|
12
|
-
return (
|
|
13
|
-
full_market.main_market.resolution
|
|
14
|
-
if full_market and full_market.is_main_market
|
|
15
|
-
else None
|
|
16
|
-
)
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
def find_full_polymarket(question: str) -> PolymarketFullMarket | None:
|
|
20
|
-
polymarket_url = find_url_to_polymarket(question)
|
|
21
|
-
return (
|
|
22
|
-
PolymarketFullMarket.fetch_from_url(polymarket_url) if polymarket_url else None
|
|
23
|
-
)
|
|
24
|
-
|
|
25
|
-
|
|
26
5
|
def find_url_to_polymarket(question: str) -> str | None:
|
|
27
6
|
# Manually create potential Polymarket's slug from the question.
|
|
28
7
|
replace_chars = {
|