prediction-market-agent-tooling 0.66.5__py3-none-any.whl → 0.66.6__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/deploy/agent.py +23 -5
- prediction_market_agent_tooling/markets/agent_market.py +9 -2
- prediction_market_agent_tooling/markets/manifold/manifold.py +3 -2
- prediction_market_agent_tooling/markets/markets.py +5 -5
- prediction_market_agent_tooling/markets/metaculus/metaculus.py +4 -2
- prediction_market_agent_tooling/markets/omen/omen.py +5 -2
- prediction_market_agent_tooling/markets/polymarket/api.py +87 -104
- prediction_market_agent_tooling/markets/polymarket/data_models.py +60 -14
- prediction_market_agent_tooling/markets/polymarket/data_models_web.py +0 -54
- prediction_market_agent_tooling/markets/polymarket/polymarket.py +109 -26
- prediction_market_agent_tooling/markets/polymarket/polymarket_subgraph_handler.py +49 -0
- prediction_market_agent_tooling/markets/polymarket/utils.py +0 -21
- prediction_market_agent_tooling/markets/seer/seer.py +14 -18
- prediction_market_agent_tooling/markets/seer/seer_subgraph_handler.py +48 -35
- prediction_market_agent_tooling/markets/seer/swap_pool_handler.py +10 -0
- prediction_market_agent_tooling/tools/cow/cow_order.py +2 -0
- prediction_market_agent_tooling/tools/httpx_cached_client.py +13 -6
- prediction_market_agent_tooling/tools/tokens/auto_deposit.py +7 -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/utils.py +5 -2
- {prediction_market_agent_tooling-0.66.5.dist-info → prediction_market_agent_tooling-0.66.6.dist-info}/METADATA +1 -1
- {prediction_market_agent_tooling-0.66.5.dist-info → prediction_market_agent_tooling-0.66.6.dist-info}/RECORD +26 -24
- {prediction_market_agent_tooling-0.66.5.dist-info → prediction_market_agent_tooling-0.66.6.dist-info}/LICENSE +0 -0
- {prediction_market_agent_tooling-0.66.5.dist-info → prediction_market_agent_tooling-0.66.6.dist-info}/WHEEL +0 -0
- {prediction_market_agent_tooling-0.66.5.dist-info → prediction_market_agent_tooling-0.66.6.dist-info}/entry_points.txt +0 -0
@@ -1,22 +1,35 @@
|
|
1
1
|
import typing as t
|
2
2
|
|
3
|
-
from prediction_market_agent_tooling.gtypes import
|
3
|
+
from prediction_market_agent_tooling.gtypes import (
|
4
|
+
USD,
|
5
|
+
CollateralToken,
|
6
|
+
HexBytes,
|
7
|
+
OutcomeStr,
|
8
|
+
Probability,
|
9
|
+
)
|
4
10
|
from prediction_market_agent_tooling.markets.agent_market import (
|
5
11
|
AgentMarket,
|
6
12
|
FilterBy,
|
7
13
|
MarketFees,
|
14
|
+
MarketType,
|
8
15
|
SortBy,
|
9
16
|
)
|
17
|
+
from prediction_market_agent_tooling.markets.data_models import Resolution
|
10
18
|
from prediction_market_agent_tooling.markets.polymarket.api import (
|
11
|
-
|
19
|
+
PolymarketOrderByEnum,
|
20
|
+
get_polymarkets_with_pagination,
|
12
21
|
)
|
13
22
|
from prediction_market_agent_tooling.markets.polymarket.data_models import (
|
14
|
-
|
23
|
+
PolymarketGammaResponseDataItem,
|
15
24
|
)
|
16
25
|
from prediction_market_agent_tooling.markets.polymarket.data_models_web import (
|
17
26
|
POLYMARKET_BASE_URL,
|
18
27
|
)
|
19
|
-
from prediction_market_agent_tooling.
|
28
|
+
from prediction_market_agent_tooling.markets.polymarket.polymarket_subgraph_handler import (
|
29
|
+
ConditionSubgraphModel,
|
30
|
+
PolymarketSubgraphHandler,
|
31
|
+
)
|
32
|
+
from prediction_market_agent_tooling.tools.datetime_utc import DatetimeUTC
|
20
33
|
|
21
34
|
|
22
35
|
class PolymarketAgentMarket(AgentMarket):
|
@@ -33,19 +46,68 @@ class PolymarketAgentMarket(AgentMarket):
|
|
33
46
|
fees: MarketFees = MarketFees.get_zero_fees()
|
34
47
|
|
35
48
|
@staticmethod
|
36
|
-
def
|
49
|
+
def build_resolution_from_condition(
|
50
|
+
condition_id: HexBytes,
|
51
|
+
condition_model_dict: dict[HexBytes, ConditionSubgraphModel],
|
52
|
+
outcomes: list[OutcomeStr],
|
53
|
+
) -> Resolution | None:
|
54
|
+
condition_model = condition_model_dict.get(condition_id)
|
55
|
+
if (
|
56
|
+
not condition_model
|
57
|
+
or condition_model.resolutionTimestamp is None
|
58
|
+
or not condition_model.payoutNumerators
|
59
|
+
or not condition_model.payoutDenominator
|
60
|
+
):
|
61
|
+
return None
|
62
|
+
|
63
|
+
# Currently we only support binary markets, hence we throw an error if we get something else.
|
64
|
+
payout_numerator_indices_gt_0 = [
|
65
|
+
idx
|
66
|
+
for idx, value in enumerate(condition_model.payoutNumerators)
|
67
|
+
if value > 0
|
68
|
+
]
|
69
|
+
# For a binary market, there should be exactly one payout numerator greater than 0.
|
70
|
+
if len(payout_numerator_indices_gt_0) != 1:
|
71
|
+
raise ValueError(
|
72
|
+
f"Only binary markets are supported. Got payout numerators: {condition_model.payoutNumerators}"
|
73
|
+
)
|
74
|
+
|
75
|
+
# we return the only payout numerator greater than 0 as resolution
|
76
|
+
resolved_outcome = outcomes[payout_numerator_indices_gt_0[0]]
|
77
|
+
return Resolution.from_answer(resolved_outcome)
|
78
|
+
|
79
|
+
@staticmethod
|
80
|
+
def from_data_model(
|
81
|
+
model: PolymarketGammaResponseDataItem,
|
82
|
+
condition_model_dict: dict[HexBytes, ConditionSubgraphModel],
|
83
|
+
) -> "PolymarketAgentMarket":
|
84
|
+
# If len(model.markets) > 0, this denotes a categorical market.
|
85
|
+
|
86
|
+
outcomes = model.markets[0].outcomes_list
|
87
|
+
outcome_prices = model.markets[0].outcome_prices
|
88
|
+
if not outcome_prices:
|
89
|
+
# We give random prices
|
90
|
+
outcome_prices = [0.5, 0.5]
|
91
|
+
probabilities = {o: Probability(op) for o, op in zip(outcomes, outcome_prices)}
|
92
|
+
|
93
|
+
resolution = PolymarketAgentMarket.build_resolution_from_condition(
|
94
|
+
condition_id=model.markets[0].conditionId,
|
95
|
+
condition_model_dict=condition_model_dict,
|
96
|
+
outcomes=outcomes,
|
97
|
+
)
|
98
|
+
|
37
99
|
return PolymarketAgentMarket(
|
38
100
|
id=model.id,
|
39
|
-
question=model.
|
101
|
+
question=model.title,
|
40
102
|
description=model.description,
|
41
|
-
outcomes=
|
42
|
-
resolution=
|
43
|
-
created_time=
|
44
|
-
close_time=model.
|
103
|
+
outcomes=outcomes,
|
104
|
+
resolution=resolution,
|
105
|
+
created_time=model.startDate,
|
106
|
+
close_time=model.endDate,
|
45
107
|
url=model.url,
|
46
|
-
volume=None,
|
108
|
+
volume=CollateralToken(model.volume) if model.volume else None,
|
47
109
|
outcome_token_pool=None,
|
48
|
-
probabilities=
|
110
|
+
probabilities=probabilities,
|
49
111
|
)
|
50
112
|
|
51
113
|
def get_tiny_bet_amount(self) -> CollateralToken:
|
@@ -61,16 +123,11 @@ class PolymarketAgentMarket(AgentMarket):
|
|
61
123
|
filter_by: FilterBy = FilterBy.OPEN,
|
62
124
|
created_after: t.Optional[DatetimeUTC] = None,
|
63
125
|
excluded_questions: set[str] | None = None,
|
64
|
-
|
65
|
-
|
126
|
+
market_type: MarketType = MarketType.ALL,
|
127
|
+
include_conditional_markets: bool = False,
|
66
128
|
) -> t.Sequence["PolymarketAgentMarket"]:
|
67
|
-
if sort_by != SortBy.NONE:
|
68
|
-
raise ValueError(f"Unsuported sort_by {sort_by} for Polymarket.")
|
69
|
-
|
70
|
-
if created_after is not None:
|
71
|
-
raise ValueError(f"Unsuported created_after for Polymarket.")
|
72
|
-
|
73
129
|
closed: bool | None
|
130
|
+
|
74
131
|
if filter_by == FilterBy.OPEN:
|
75
132
|
closed = False
|
76
133
|
elif filter_by == FilterBy.RESOLVED:
|
@@ -80,11 +137,37 @@ class PolymarketAgentMarket(AgentMarket):
|
|
80
137
|
else:
|
81
138
|
raise ValueError(f"Unknown filter_by: {filter_by}")
|
82
139
|
|
140
|
+
ascending: bool = False # default value
|
141
|
+
match sort_by:
|
142
|
+
case SortBy.NEWEST:
|
143
|
+
order_by = PolymarketOrderByEnum.START_DATE
|
144
|
+
case SortBy.CLOSING_SOONEST:
|
145
|
+
ascending = True
|
146
|
+
order_by = PolymarketOrderByEnum.END_DATE
|
147
|
+
case SortBy.HIGHEST_LIQUIDITY:
|
148
|
+
order_by = PolymarketOrderByEnum.LIQUIDITY
|
149
|
+
case SortBy.NONE:
|
150
|
+
order_by = PolymarketOrderByEnum.VOLUME_24HR
|
151
|
+
case _:
|
152
|
+
raise ValueError(f"Unknown sort_by: {sort_by}")
|
153
|
+
|
154
|
+
# closed markets also have property active=True, hence ignoring active.
|
155
|
+
markets = get_polymarkets_with_pagination(
|
156
|
+
limit=limit,
|
157
|
+
closed=closed,
|
158
|
+
order_by=order_by,
|
159
|
+
ascending=ascending,
|
160
|
+
created_after=created_after,
|
161
|
+
excluded_questions=excluded_questions,
|
162
|
+
only_binary=market_type is not MarketType.CATEGORICAL,
|
163
|
+
)
|
164
|
+
|
165
|
+
condition_models = PolymarketSubgraphHandler().get_conditions(
|
166
|
+
condition_ids=[market.markets[0].conditionId for market in markets]
|
167
|
+
)
|
168
|
+
condition_models_dict = {c.id: c for c in condition_models}
|
169
|
+
|
83
170
|
return [
|
84
|
-
PolymarketAgentMarket.from_data_model(m)
|
85
|
-
for m in
|
86
|
-
limit=limit,
|
87
|
-
closed=closed,
|
88
|
-
excluded_questions=excluded_questions,
|
89
|
-
)
|
171
|
+
PolymarketAgentMarket.from_data_model(m, condition_models_dict)
|
172
|
+
for m in markets
|
90
173
|
]
|
@@ -0,0 +1,49 @@
|
|
1
|
+
from pydantic import BaseModel
|
2
|
+
|
3
|
+
from prediction_market_agent_tooling.gtypes import HexBytes
|
4
|
+
from prediction_market_agent_tooling.markets.base_subgraph_handler import (
|
5
|
+
BaseSubgraphHandler,
|
6
|
+
)
|
7
|
+
|
8
|
+
|
9
|
+
class ConditionSubgraphModel(BaseModel):
|
10
|
+
id: HexBytes
|
11
|
+
payoutDenominator: int | None = None
|
12
|
+
payoutNumerators: list[int] | None = None
|
13
|
+
outcomeSlotCount: int
|
14
|
+
resolutionTimestamp: int | None = None
|
15
|
+
|
16
|
+
|
17
|
+
class PolymarketSubgraphHandler(BaseSubgraphHandler):
|
18
|
+
POLYMARKET_CONDITIONS_SUBGRAPH = "https://gateway.thegraph.com/api/{graph_api_key}/subgraphs/id/81Dm16JjuFSrqz813HysXoUPvzTwE7fsfPk2RTf66nyC"
|
19
|
+
|
20
|
+
def __init__(self) -> None:
|
21
|
+
super().__init__()
|
22
|
+
|
23
|
+
# Load the subgraph
|
24
|
+
self.conditions_subgraph = self.sg.load_subgraph(
|
25
|
+
self.POLYMARKET_CONDITIONS_SUBGRAPH.format(
|
26
|
+
graph_api_key=self.keys.graph_api_key.get_secret_value()
|
27
|
+
)
|
28
|
+
)
|
29
|
+
|
30
|
+
def get_conditions(
|
31
|
+
self, condition_ids: list[HexBytes]
|
32
|
+
) -> list[ConditionSubgraphModel]:
|
33
|
+
where_stms = {"id_in": [i.hex() for i in condition_ids]}
|
34
|
+
conditions = self.conditions_subgraph.Query.conditions(
|
35
|
+
where=where_stms,
|
36
|
+
)
|
37
|
+
|
38
|
+
condition_fields = [
|
39
|
+
conditions.id,
|
40
|
+
conditions.payoutNumerators,
|
41
|
+
conditions.payoutDenominator,
|
42
|
+
conditions.outcomeSlotCount,
|
43
|
+
conditions.resolutionTimestamp,
|
44
|
+
]
|
45
|
+
|
46
|
+
conditions_models = self.do_query(
|
47
|
+
fields=condition_fields, pydantic_model=ConditionSubgraphModel
|
48
|
+
)
|
49
|
+
return conditions_models
|
@@ -1,28 +1,7 @@
|
|
1
|
-
from prediction_market_agent_tooling.markets.data_models import Resolution
|
2
1
|
from prediction_market_agent_tooling.markets.markets import MarketType
|
3
|
-
from prediction_market_agent_tooling.markets.polymarket.data_models_web import (
|
4
|
-
PolymarketFullMarket,
|
5
|
-
)
|
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 = {
|
@@ -24,6 +24,8 @@ from prediction_market_agent_tooling.loggers import logger
|
|
24
24
|
from prediction_market_agent_tooling.markets.agent_market import (
|
25
25
|
AgentMarket,
|
26
26
|
FilterBy,
|
27
|
+
MarketFees,
|
28
|
+
MarketType,
|
27
29
|
ProcessedMarket,
|
28
30
|
ProcessedTradedMarket,
|
29
31
|
SortBy,
|
@@ -76,6 +78,9 @@ from prediction_market_agent_tooling.tools.datetime_utc import DatetimeUTC
|
|
76
78
|
from prediction_market_agent_tooling.tools.tokens.auto_deposit import (
|
77
79
|
auto_deposit_collateral_token,
|
78
80
|
)
|
81
|
+
from prediction_market_agent_tooling.tools.tokens.slippage import (
|
82
|
+
get_slippage_tolerance_per_token,
|
83
|
+
)
|
79
84
|
from prediction_market_agent_tooling.tools.tokens.usd import (
|
80
85
|
get_token_in_usd,
|
81
86
|
get_usd_in_token,
|
@@ -95,6 +100,7 @@ class SeerAgentMarket(AgentMarket):
|
|
95
100
|
None # Seer markets don't have a description, so just default to None.
|
96
101
|
)
|
97
102
|
outcomes_supply: int
|
103
|
+
minimum_market_liquidity_required: CollateralToken = CollateralToken(1)
|
98
104
|
|
99
105
|
def get_collateral_token_contract(
|
100
106
|
self, web3: Web3 | None = None
|
@@ -231,9 +237,7 @@ class SeerAgentMarket(AgentMarket):
|
|
231
237
|
def get_tiny_bet_amount(self) -> CollateralToken:
|
232
238
|
return self.get_in_token(SEER_TINY_BET_AMOUNT)
|
233
239
|
|
234
|
-
def
|
235
|
-
self, user_id: str, web3: Web3 | None = None
|
236
|
-
) -> ExistingPosition:
|
240
|
+
def get_position(self, user_id: str, web3: Web3 | None = None) -> ExistingPosition:
|
237
241
|
"""
|
238
242
|
Fetches position from the user in a given market.
|
239
243
|
We ignore the INVALID balances since we are only interested in binary outcomes.
|
@@ -264,15 +268,6 @@ class SeerAgentMarket(AgentMarket):
|
|
264
268
|
amounts_ot=amounts_ot,
|
265
269
|
)
|
266
270
|
|
267
|
-
def get_position(
|
268
|
-
self, user_id: str, web3: Web3 | None = None
|
269
|
-
) -> ExistingPosition | None:
|
270
|
-
try:
|
271
|
-
return self.get_position_else_raise(user_id=user_id, web3=web3)
|
272
|
-
except Exception as e:
|
273
|
-
logger.warning(f"Could not get position for user {user_id}, exception {e}")
|
274
|
-
return None
|
275
|
-
|
276
271
|
@staticmethod
|
277
272
|
def get_user_id(api_keys: APIKeys) -> str:
|
278
273
|
return OmenAgentMarket.get_user_id(api_keys)
|
@@ -409,16 +404,16 @@ class SeerAgentMarket(AgentMarket):
|
|
409
404
|
filter_by: FilterBy = FilterBy.OPEN,
|
410
405
|
created_after: t.Optional[DatetimeUTC] = None,
|
411
406
|
excluded_questions: set[str] | None = None,
|
412
|
-
|
413
|
-
|
407
|
+
market_type: MarketType = MarketType.ALL,
|
408
|
+
include_conditional_markets: bool = False,
|
414
409
|
) -> t.Sequence["SeerAgentMarket"]:
|
415
410
|
seer_subgraph = SeerSubgraphHandler()
|
411
|
+
|
416
412
|
markets = seer_subgraph.get_markets(
|
417
413
|
limit=limit,
|
418
414
|
sort_by=sort_by,
|
419
415
|
filter_by=filter_by,
|
420
|
-
|
421
|
-
include_only_scalar_markets=fetch_scalar_markets,
|
416
|
+
market_type=market_type,
|
422
417
|
include_conditional_markets=False,
|
423
418
|
)
|
424
419
|
|
@@ -501,7 +496,7 @@ class SeerAgentMarket(AgentMarket):
|
|
501
496
|
|
502
497
|
def has_liquidity_for_outcome(self, outcome: OutcomeStr) -> bool:
|
503
498
|
liquidity = self.get_liquidity_for_outcome(outcome)
|
504
|
-
return liquidity >
|
499
|
+
return liquidity > self.minimum_market_liquidity_required
|
505
500
|
|
506
501
|
def has_liquidity(self) -> bool:
|
507
502
|
# We define a market as having liquidity if it has liquidity for all outcomes except for the invalid (index -1)
|
@@ -534,7 +529,7 @@ class SeerAgentMarket(AgentMarket):
|
|
534
529
|
Returns:
|
535
530
|
Transaction hash of the successful swap
|
536
531
|
"""
|
537
|
-
|
532
|
+
slippage_tolerance = get_slippage_tolerance_per_token(sell_token, buy_token)
|
538
533
|
try:
|
539
534
|
_, order = swap_tokens_waiting(
|
540
535
|
amount_wei=amount_wei,
|
@@ -544,6 +539,7 @@ class SeerAgentMarket(AgentMarket):
|
|
544
539
|
web3=web3,
|
545
540
|
wait_order_complete=False,
|
546
541
|
timeout=timedelta(minutes=2),
|
542
|
+
slippage_tolerance=slippage_tolerance,
|
547
543
|
)
|
548
544
|
order_metadata = asyncio.run(wait_for_order_completion(order=order))
|
549
545
|
logger.debug(
|
@@ -1,5 +1,6 @@
|
|
1
1
|
import sys
|
2
2
|
import typing as t
|
3
|
+
from enum import Enum
|
3
4
|
from typing import Any
|
4
5
|
|
5
6
|
from subgrounds import FieldPath
|
@@ -13,7 +14,11 @@ from prediction_market_agent_tooling.deploy.constants import (
|
|
13
14
|
)
|
14
15
|
from prediction_market_agent_tooling.gtypes import ChecksumAddress, Wei
|
15
16
|
from prediction_market_agent_tooling.loggers import logger
|
16
|
-
from prediction_market_agent_tooling.markets.agent_market import
|
17
|
+
from prediction_market_agent_tooling.markets.agent_market import (
|
18
|
+
FilterBy,
|
19
|
+
MarketType,
|
20
|
+
SortBy,
|
21
|
+
)
|
17
22
|
from prediction_market_agent_tooling.markets.base_subgraph_handler import (
|
18
23
|
BaseSubgraphHandler,
|
19
24
|
)
|
@@ -24,6 +29,14 @@ from prediction_market_agent_tooling.tools.utils import to_int_timestamp, utcnow
|
|
24
29
|
from prediction_market_agent_tooling.tools.web3_utils import unwrap_generic_value
|
25
30
|
|
26
31
|
|
32
|
+
class TemplateId(int, Enum):
|
33
|
+
"""Template IDs used in Reality.eth questions."""
|
34
|
+
|
35
|
+
SCALAR = 1
|
36
|
+
CATEGORICAL = 2
|
37
|
+
MULTICATEGORICAL = 3
|
38
|
+
|
39
|
+
|
27
40
|
class SeerSubgraphHandler(BaseSubgraphHandler):
|
28
41
|
"""
|
29
42
|
Class responsible for handling interactions with Seer subgraphs.
|
@@ -70,6 +83,7 @@ class SeerSubgraphHandler(BaseSubgraphHandler):
|
|
70
83
|
markets_field.collateralToken,
|
71
84
|
markets_field.upperBound,
|
72
85
|
markets_field.lowerBound,
|
86
|
+
markets_field.templateId,
|
73
87
|
]
|
74
88
|
return fields
|
75
89
|
|
@@ -100,8 +114,7 @@ class SeerSubgraphHandler(BaseSubgraphHandler):
|
|
100
114
|
filter_by: FilterBy,
|
101
115
|
outcome_supply_gt_if_open: Wei,
|
102
116
|
include_conditional_markets: bool = False,
|
103
|
-
|
104
|
-
include_only_scalar_markets: bool = False,
|
117
|
+
market_type: MarketType = MarketType.ALL,
|
105
118
|
) -> dict[Any, Any]:
|
106
119
|
now = to_int_timestamp(utcnow())
|
107
120
|
|
@@ -123,43 +136,47 @@ class SeerSubgraphHandler(BaseSubgraphHandler):
|
|
123
136
|
if not include_conditional_markets:
|
124
137
|
and_stms["parentMarket"] = ADDRESS_ZERO.lower()
|
125
138
|
|
126
|
-
|
127
|
-
exclude_scalar_yes, exclude_scalar_no = {}, {}
|
139
|
+
outcome_filters: list[dict[str, t.Any]] = []
|
128
140
|
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
141
|
+
if market_type == MarketType.SCALAR:
|
142
|
+
# Template ID "1" + UP/DOWN outcomes for scalar markets
|
143
|
+
and_stms["templateId"] = TemplateId.SCALAR.value
|
144
|
+
up_filter = SeerSubgraphHandler._create_case_variations_condition(
|
133
145
|
UP_OUTCOME_LOWERCASE_IDENTIFIER, "outcomes_contains", "or"
|
134
146
|
)
|
135
|
-
|
147
|
+
down_filter = SeerSubgraphHandler._create_case_variations_condition(
|
136
148
|
DOWN_OUTCOME_LOWERCASE_IDENTIFIER, "outcomes_contains", "or"
|
137
149
|
)
|
138
|
-
|
139
|
-
|
140
|
-
|
150
|
+
outcome_filters.extend([up_filter, down_filter])
|
151
|
+
|
152
|
+
elif market_type == MarketType.BINARY:
|
153
|
+
# Template ID "2" + YES/NO outcomes for binary markets
|
154
|
+
and_stms["templateId"] = TemplateId.CATEGORICAL.value
|
155
|
+
yes_filter = SeerSubgraphHandler._create_case_variations_condition(
|
141
156
|
YES_OUTCOME_LOWERCASE_IDENTIFIER, "outcomes_contains", "or"
|
142
157
|
)
|
143
|
-
|
158
|
+
no_filter = SeerSubgraphHandler._create_case_variations_condition(
|
144
159
|
NO_OUTCOME_LOWERCASE_IDENTIFIER, "outcomes_contains", "or"
|
145
160
|
)
|
161
|
+
outcome_filters.extend([yes_filter, no_filter])
|
146
162
|
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
163
|
+
elif market_type == MarketType.CATEGORICAL:
|
164
|
+
# Template ID 2 (categorical) OR Template ID 3 (multi-categorical,
|
165
|
+
# we treat them as categorical for now for simplicity)
|
166
|
+
# https://reality.eth.limo/app/docs/html/contracts.html#templates
|
167
|
+
outcome_filters.append(
|
168
|
+
{
|
169
|
+
"or": [
|
170
|
+
{"templateId": TemplateId.CATEGORICAL.value},
|
171
|
+
{"templateId": TemplateId.MULTICATEGORICAL.value},
|
172
|
+
]
|
173
|
+
}
|
158
174
|
)
|
159
175
|
|
160
|
-
|
161
|
-
|
162
|
-
|
176
|
+
# If none specified, don't add any template/outcome filters (returns all types)
|
177
|
+
|
178
|
+
all_filters = [and_stms] + outcome_filters if and_stms else outcome_filters
|
179
|
+
where_stms: dict[str, t.Any] = {"and": all_filters}
|
163
180
|
return where_stms
|
164
181
|
|
165
182
|
def _build_sort_params(
|
@@ -194,20 +211,16 @@ class SeerSubgraphHandler(BaseSubgraphHandler):
|
|
194
211
|
sort_by: SortBy = SortBy.NONE,
|
195
212
|
limit: int | None = None,
|
196
213
|
outcome_supply_gt_if_open: Wei = Wei(0),
|
197
|
-
|
198
|
-
|
199
|
-
include_only_scalar_markets: bool = False,
|
214
|
+
market_type: MarketType = MarketType.ALL,
|
215
|
+
include_conditional_markets: bool = False,
|
200
216
|
) -> list[SeerMarket]:
|
201
217
|
sort_direction, sort_by_field = self._build_sort_params(sort_by)
|
202
218
|
|
203
|
-
"""Returns markets that contain 2 categories plus an invalid outcome."""
|
204
|
-
# Binary markets on Seer contain 3 outcomes: OutcomeA, outcomeB and an Invalid option.
|
205
219
|
where_stms = self._build_where_statements(
|
206
220
|
filter_by=filter_by,
|
207
221
|
outcome_supply_gt_if_open=outcome_supply_gt_if_open,
|
208
222
|
include_conditional_markets=include_conditional_markets,
|
209
|
-
|
210
|
-
include_only_scalar_markets=include_only_scalar_markets,
|
223
|
+
market_type=market_type,
|
211
224
|
)
|
212
225
|
|
213
226
|
# These values can not be set to `None`, but they can be omitted.
|
@@ -19,6 +19,7 @@ from prediction_market_agent_tooling.markets.seer.seer_contracts import (
|
|
19
19
|
from prediction_market_agent_tooling.markets.seer.seer_subgraph_handler import (
|
20
20
|
SeerSubgraphHandler,
|
21
21
|
)
|
22
|
+
from prediction_market_agent_tooling.tools.contract import ContractERC20OnGnosisChain
|
22
23
|
|
23
24
|
|
24
25
|
class SwapPoolHandler:
|
@@ -90,6 +91,15 @@ class SwapPoolHandler:
|
|
90
91
|
amount_out_minimum=amount_out_minimum,
|
91
92
|
)
|
92
93
|
|
94
|
+
# make sure user has enough tokens to sell
|
95
|
+
balance_collateral_token = ContractERC20OnGnosisChain(
|
96
|
+
address=token_in
|
97
|
+
).balanceOf(self.api_keys.bet_from_address, web3=web3)
|
98
|
+
if balance_collateral_token < amount_wei:
|
99
|
+
raise ValueError(
|
100
|
+
f"Balance {balance_collateral_token} of {token_in} insufficient for trade, required {amount_wei}"
|
101
|
+
)
|
102
|
+
|
93
103
|
tx_receipt = SwaprRouterContract().exact_input_single(
|
94
104
|
api_keys=self.api_keys, params=p, web3=web3
|
95
105
|
)
|
@@ -211,6 +211,7 @@ def swap_tokens_waiting(
|
|
211
211
|
web3: Web3 | None = None,
|
212
212
|
wait_order_complete: bool = True,
|
213
213
|
timeout: timedelta = timedelta(seconds=120),
|
214
|
+
slippage_tolerance: float = 0.01,
|
214
215
|
) -> tuple[OrderMetaData | None, CompletedOrder]:
|
215
216
|
# CoW library uses async, so we need to wrap the call in asyncio.run for us to use it.
|
216
217
|
return asyncio.run(
|
@@ -224,6 +225,7 @@ def swap_tokens_waiting(
|
|
224
225
|
timeout=timeout,
|
225
226
|
web3=web3,
|
226
227
|
wait_order_complete=wait_order_complete,
|
228
|
+
slippage_tolerance=slippage_tolerance,
|
227
229
|
)
|
228
230
|
)
|
229
231
|
|
@@ -1,14 +1,21 @@
|
|
1
1
|
import hishel
|
2
|
+
import httpx
|
2
3
|
|
4
|
+
from prediction_market_agent_tooling.tools.singleton import SingletonMeta
|
3
5
|
|
4
|
-
|
5
|
-
|
6
|
+
ONE_DAY_IN_SECONDS = 60 * 60 * 24
|
7
|
+
|
8
|
+
|
9
|
+
class HttpxCachedClient(metaclass=SingletonMeta):
|
10
|
+
def __init__(self, ttl: int = ONE_DAY_IN_SECONDS) -> None:
|
6
11
|
storage = hishel.FileStorage(
|
7
|
-
ttl=
|
8
|
-
check_ttl_every=
|
12
|
+
ttl=ttl,
|
13
|
+
check_ttl_every=60,
|
9
14
|
)
|
10
15
|
controller = hishel.Controller(force_cache=True)
|
11
|
-
self.client = hishel.CacheClient(
|
16
|
+
self.client: httpx.Client = hishel.CacheClient(
|
17
|
+
storage=storage, controller=controller
|
18
|
+
)
|
12
19
|
|
13
|
-
def get_client(self) ->
|
20
|
+
def get_client(self) -> httpx.Client:
|
14
21
|
return self.client
|
@@ -14,6 +14,9 @@ from prediction_market_agent_tooling.tools.cow.cow_order import (
|
|
14
14
|
swap_tokens_waiting,
|
15
15
|
)
|
16
16
|
from prediction_market_agent_tooling.tools.tokens.main_token import KEEPING_ERC20_TOKEN
|
17
|
+
from prediction_market_agent_tooling.tools.tokens.slippage import (
|
18
|
+
get_slippage_tolerance_per_token,
|
19
|
+
)
|
17
20
|
from prediction_market_agent_tooling.tools.tokens.usd import get_usd_in_token
|
18
21
|
from prediction_market_agent_tooling.tools.utils import should_not_happen
|
19
22
|
|
@@ -156,10 +159,14 @@ def auto_deposit_erc20(
|
|
156
159
|
raise ValueError(
|
157
160
|
"Not enough of the source token to sell to get the desired amount of the collateral token."
|
158
161
|
)
|
162
|
+
slippage_tolerance = get_slippage_tolerance_per_token(
|
163
|
+
KEEPING_ERC20_TOKEN.address, collateral_token_contract.address
|
164
|
+
)
|
159
165
|
swap_tokens_waiting(
|
160
166
|
amount_wei=amount_to_sell_wei,
|
161
167
|
sell_token=KEEPING_ERC20_TOKEN.address,
|
162
168
|
buy_token=collateral_token_contract.address,
|
163
169
|
api_keys=api_keys,
|
164
170
|
web3=web3,
|
171
|
+
slippage_tolerance=slippage_tolerance,
|
165
172
|
)
|
@@ -9,6 +9,9 @@ from prediction_market_agent_tooling.tools.contract import (
|
|
9
9
|
)
|
10
10
|
from prediction_market_agent_tooling.tools.cow.cow_order import swap_tokens_waiting
|
11
11
|
from prediction_market_agent_tooling.tools.tokens.main_token import KEEPING_ERC20_TOKEN
|
12
|
+
from prediction_market_agent_tooling.tools.tokens.slippage import (
|
13
|
+
get_slippage_tolerance_per_token,
|
14
|
+
)
|
12
15
|
from prediction_market_agent_tooling.tools.utils import should_not_happen
|
13
16
|
|
14
17
|
|
@@ -49,12 +52,17 @@ def auto_withdraw_collateral_token(
|
|
49
52
|
f"Swapping {amount_wei.as_token} from {collateral_token_contract.symbol_cached(web3)} into {KEEPING_ERC20_TOKEN.symbol_cached(web3)}"
|
50
53
|
)
|
51
54
|
# Otherwise, DEX will handle the rest of token swaps.
|
55
|
+
slippage_tolerance = get_slippage_tolerance_per_token(
|
56
|
+
collateral_token_contract.address,
|
57
|
+
KEEPING_ERC20_TOKEN.address,
|
58
|
+
)
|
52
59
|
swap_tokens_waiting(
|
53
60
|
amount_wei=amount_wei,
|
54
61
|
sell_token=collateral_token_contract.address,
|
55
62
|
buy_token=KEEPING_ERC20_TOKEN.address,
|
56
63
|
api_keys=api_keys,
|
57
64
|
web3=web3,
|
65
|
+
slippage_tolerance=slippage_tolerance,
|
58
66
|
)
|
59
67
|
else:
|
60
68
|
should_not_happen("Unsupported ERC20 contract type.")
|
@@ -0,0 +1,21 @@
|
|
1
|
+
from prediction_market_agent_tooling.gtypes import ChecksumAddress
|
2
|
+
from prediction_market_agent_tooling.markets.omen.omen_constants import (
|
3
|
+
METRI_SUPER_GROUP_CONTRACT_ADDRESS,
|
4
|
+
)
|
5
|
+
|
6
|
+
DEFAULT_SLIPPAGE_TOLERANCE = 0.05
|
7
|
+
|
8
|
+
SLIPPAGE_TOLERANCE_PER_TOKEN = {
|
9
|
+
METRI_SUPER_GROUP_CONTRACT_ADDRESS: 0.1,
|
10
|
+
}
|
11
|
+
|
12
|
+
|
13
|
+
def get_slippage_tolerance_per_token(
|
14
|
+
sell_token: ChecksumAddress,
|
15
|
+
buy_token: ChecksumAddress,
|
16
|
+
default_slippage: float = DEFAULT_SLIPPAGE_TOLERANCE,
|
17
|
+
) -> float:
|
18
|
+
return max(
|
19
|
+
SLIPPAGE_TOLERANCE_PER_TOKEN.get(sell_token, default_slippage),
|
20
|
+
SLIPPAGE_TOLERANCE_PER_TOKEN.get(buy_token, default_slippage),
|
21
|
+
)
|