prediction-market-agent-tooling 0.66.5__py3-none-any.whl → 0.67.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/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 +3 -1
- 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/price_manager.py +65 -46
- prediction_market_agent_tooling/markets/seer/seer.py +70 -90
- prediction_market_agent_tooling/markets/seer/seer_subgraph_handler.py +48 -35
- prediction_market_agent_tooling/markets/seer/swap_pool_handler.py +11 -1
- prediction_market_agent_tooling/tools/cow/cow_order.py +26 -11
- prediction_market_agent_tooling/tools/cow/models.py +4 -2
- 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.67.1.dist-info}/METADATA +1 -1
- {prediction_market_agent_tooling-0.66.5.dist-info → prediction_market_agent_tooling-0.67.1.dist-info}/RECORD +28 -26
- {prediction_market_agent_tooling-0.66.5.dist-info → prediction_market_agent_tooling-0.67.1.dist-info}/LICENSE +0 -0
- {prediction_market_agent_tooling-0.66.5.dist-info → prediction_market_agent_tooling-0.67.1.dist-info}/WHEEL +0 -0
- {prediction_market_agent_tooling-0.66.5.dist-info → prediction_market_agent_tooling-0.67.1.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 = {
|
@@ -1,6 +1,5 @@
|
|
1
|
-
import typing as t
|
2
|
-
|
3
1
|
from cachetools import TTLCache, cached
|
2
|
+
from pydantic import BaseModel
|
4
3
|
from web3 import Web3
|
5
4
|
|
6
5
|
from prediction_market_agent_tooling.gtypes import (
|
@@ -18,32 +17,15 @@ from prediction_market_agent_tooling.markets.seer.exceptions import (
|
|
18
17
|
from prediction_market_agent_tooling.markets.seer.seer_subgraph_handler import (
|
19
18
|
SeerSubgraphHandler,
|
20
19
|
)
|
21
|
-
from prediction_market_agent_tooling.markets.seer.subgraph_data_models import SeerPool
|
22
20
|
from prediction_market_agent_tooling.tools.cow.cow_order import (
|
23
21
|
get_buy_token_amount_else_raise,
|
24
22
|
)
|
25
23
|
from prediction_market_agent_tooling.tools.hexbytes_custom import HexBytes
|
26
24
|
|
27
25
|
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
collateral_exchange_amount: CollateralToken | None = None,
|
32
|
-
) -> str:
|
33
|
-
"""
|
34
|
-
Generate a unique cache key based on a token address and optional collateral token.
|
35
|
-
"""
|
36
|
-
|
37
|
-
if collateral_exchange_amount is None:
|
38
|
-
return f"{token}-no_collateral"
|
39
|
-
|
40
|
-
return "-".join(
|
41
|
-
[
|
42
|
-
token,
|
43
|
-
collateral_exchange_amount.symbol,
|
44
|
-
str(collateral_exchange_amount.value),
|
45
|
-
]
|
46
|
-
)
|
26
|
+
class Prices(BaseModel):
|
27
|
+
priceOfCollateralInAskingToken: CollateralToken
|
28
|
+
priceOfAskingTokenInCollateral: CollateralToken
|
47
29
|
|
48
30
|
|
49
31
|
class PriceManager:
|
@@ -67,17 +49,17 @@ class PriceManager:
|
|
67
49
|
f"{price_diff_pct=} larger than {max_price_diff=} for seer market {self.seer_market.id.hex()} "
|
68
50
|
)
|
69
51
|
|
70
|
-
|
71
|
-
|
52
|
+
def get_price_for_token(self, token: ChecksumAddress) -> CollateralToken | None:
|
53
|
+
return self.get_amount_of_token_in_collateral(token, CollateralToken(1))
|
54
|
+
|
55
|
+
@cached(TTLCache(maxsize=100, ttl=5 * 60))
|
56
|
+
def get_amount_of_collateral_in_token(
|
72
57
|
self,
|
73
58
|
token: ChecksumAddress,
|
74
|
-
collateral_exchange_amount: CollateralToken
|
59
|
+
collateral_exchange_amount: CollateralToken,
|
75
60
|
) -> CollateralToken | None:
|
76
|
-
|
77
|
-
collateral_exchange_amount
|
78
|
-
if collateral_exchange_amount is not None
|
79
|
-
else CollateralToken(1)
|
80
|
-
)
|
61
|
+
if token == self.seer_market.collateral_token_contract_address_checksummed:
|
62
|
+
return collateral_exchange_amount
|
81
63
|
|
82
64
|
try:
|
83
65
|
buy_token_amount = get_buy_token_amount_else_raise(
|
@@ -85,23 +67,51 @@ class PriceManager:
|
|
85
67
|
sell_token=self.seer_market.collateral_token_contract_address_checksummed,
|
86
68
|
buy_token=token,
|
87
69
|
)
|
88
|
-
|
89
|
-
return CollateralToken(price)
|
70
|
+
return buy_token_amount.as_token
|
90
71
|
|
91
72
|
except Exception as e:
|
92
73
|
logger.warning(
|
93
74
|
f"Could not get quote for {token=} from Cow, exception {e=}. Falling back to pools. "
|
94
75
|
)
|
95
|
-
|
76
|
+
prices = self.get_token_price_from_pools(token=token)
|
77
|
+
return (
|
78
|
+
prices.priceOfCollateralInAskingToken * collateral_exchange_amount
|
79
|
+
if prices
|
80
|
+
else None
|
81
|
+
)
|
96
82
|
|
97
|
-
@
|
98
|
-
def
|
99
|
-
|
83
|
+
@cached(TTLCache(maxsize=100, ttl=5 * 60))
|
84
|
+
def get_amount_of_token_in_collateral(
|
85
|
+
self,
|
86
|
+
token: ChecksumAddress,
|
87
|
+
token_exchange_amount: CollateralToken,
|
88
|
+
) -> CollateralToken | None:
|
89
|
+
if token == self.seer_market.collateral_token_contract_address_checksummed:
|
90
|
+
return token_exchange_amount
|
91
|
+
|
92
|
+
try:
|
93
|
+
buy_collateral_amount = get_buy_token_amount_else_raise(
|
94
|
+
sell_amount=token_exchange_amount.as_wei,
|
95
|
+
sell_token=token,
|
96
|
+
buy_token=self.seer_market.collateral_token_contract_address_checksummed,
|
97
|
+
)
|
98
|
+
return buy_collateral_amount.as_token
|
99
|
+
|
100
|
+
except Exception as e:
|
101
|
+
logger.warning(
|
102
|
+
f"Could not get quote for {token=} from Cow, exception {e=}. Falling back to pools. "
|
103
|
+
)
|
104
|
+
prices = self.get_token_price_from_pools(token=token)
|
105
|
+
return (
|
106
|
+
prices.priceOfAskingTokenInCollateral * token_exchange_amount
|
107
|
+
if prices
|
108
|
+
else None
|
109
|
+
)
|
100
110
|
|
101
111
|
def get_token_price_from_pools(
|
102
112
|
self,
|
103
113
|
token: ChecksumAddress,
|
104
|
-
) ->
|
114
|
+
) -> Prices | None:
|
105
115
|
pool = SeerSubgraphHandler().get_pool_by_token(
|
106
116
|
token_address=token,
|
107
117
|
collateral_address=self.seer_market.collateral_token_contract_address_checksummed,
|
@@ -111,15 +121,24 @@ class PriceManager:
|
|
111
121
|
logger.warning(f"Could not find a pool for {token=}")
|
112
122
|
return None
|
113
123
|
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
124
|
+
if (
|
125
|
+
Web3.to_checksum_address(pool.token0.id)
|
126
|
+
== self.seer_market.collateral_token_contract_address_checksummed
|
127
|
+
):
|
128
|
+
price_coll_in_asking = (
|
129
|
+
pool.token1Price
|
130
|
+
) # how many outcome tokens per 1 collateral
|
131
|
+
price_asking_in_coll = (
|
132
|
+
pool.token0Price
|
133
|
+
) # how many collateral tokens per 1 outcome
|
134
|
+
else:
|
135
|
+
price_coll_in_asking = pool.token0Price
|
136
|
+
price_asking_in_coll = pool.token1Price
|
137
|
+
|
138
|
+
return Prices(
|
139
|
+
priceOfCollateralInAskingToken=price_coll_in_asking,
|
140
|
+
priceOfAskingTokenInCollateral=price_asking_in_coll,
|
121
141
|
)
|
122
|
-
return price
|
123
142
|
|
124
143
|
def build_probability_map(self) -> dict[OutcomeStr, Probability]:
|
125
144
|
# Inspired by https://github.com/seer-pm/demo/blob/ca682153a6b4d4dd3dcc4ad8bdcbe32202fc8fe7/web/src/hooks/useMarketOdds.ts#L15
|
@@ -160,6 +179,6 @@ class PriceManager:
|
|
160
179
|
outcome = self.seer_market.outcomes[
|
161
180
|
self.seer_market.wrapped_tokens.index(outcome_token)
|
162
181
|
]
|
163
|
-
normalized_prices[
|
182
|
+
normalized_prices[outcome] = new_price
|
164
183
|
|
165
184
|
return normalized_prices
|