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,7 +1,10 @@
|
|
|
1
1
|
import typing as t
|
|
2
|
+
from datetime import timedelta
|
|
3
|
+
from enum import Enum
|
|
4
|
+
from typing import Annotated
|
|
2
5
|
from urllib.parse import urljoin
|
|
3
6
|
|
|
4
|
-
from pydantic import BaseModel, ConfigDict, Field
|
|
7
|
+
from pydantic import BaseModel, BeforeValidator, ConfigDict, Field, computed_field
|
|
5
8
|
from web3 import Web3
|
|
6
9
|
from web3.constants import ADDRESS_ZERO
|
|
7
10
|
|
|
@@ -13,12 +16,11 @@ from prediction_market_agent_tooling.gtypes import (
|
|
|
13
16
|
OutcomeStr,
|
|
14
17
|
OutcomeWei,
|
|
15
18
|
Web3Wei,
|
|
16
|
-
|
|
17
|
-
from prediction_market_agent_tooling.markets.seer.subgraph_data_models import (
|
|
18
|
-
SeerParentMarket,
|
|
19
|
+
Wei,
|
|
19
20
|
)
|
|
20
21
|
from prediction_market_agent_tooling.tools.contract import ContractERC20OnGnosisChain
|
|
21
22
|
from prediction_market_agent_tooling.tools.datetime_utc import DatetimeUTC
|
|
23
|
+
from prediction_market_agent_tooling.tools.utils import utcnow
|
|
22
24
|
|
|
23
25
|
|
|
24
26
|
class CreateCategoricalMarketsParams(BaseModel):
|
|
@@ -47,6 +49,36 @@ class CreateCategoricalMarketsParams(BaseModel):
|
|
|
47
49
|
SEER_BASE_URL = "https://app.seer.pm"
|
|
48
50
|
|
|
49
51
|
|
|
52
|
+
def seer_normalize_wei(value: int | dict[str, t.Any] | None) -> int | None:
|
|
53
|
+
# See https://github.com/seer-pm/demo/blob/main/web/netlify/edge-functions/utils/common.ts#L22
|
|
54
|
+
if value is None:
|
|
55
|
+
return value
|
|
56
|
+
elif isinstance(value, dict):
|
|
57
|
+
if value.get("value") is None:
|
|
58
|
+
raise ValueError(f"Expected a dictionary with a value key, but got {value}")
|
|
59
|
+
value = int(value["value"])
|
|
60
|
+
is_in_wei = value > 1e10
|
|
61
|
+
return value if is_in_wei else value * 10**18
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
SeerNormalizedWei = Annotated[Wei | None, BeforeValidator(seer_normalize_wei)]
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
class MarketId(BaseModel):
|
|
68
|
+
id: HexBytes
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
class SeerQuestion(BaseModel):
|
|
72
|
+
id: str
|
|
73
|
+
best_answer: HexBytes
|
|
74
|
+
finalize_ts: int
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
class SeerMarketQuestions(BaseModel):
|
|
78
|
+
question: SeerQuestion
|
|
79
|
+
market: MarketId
|
|
80
|
+
|
|
81
|
+
|
|
50
82
|
class SeerMarket(BaseModel):
|
|
51
83
|
model_config = ConfigDict(populate_by_name=True)
|
|
52
84
|
|
|
@@ -55,10 +87,11 @@ class SeerMarket(BaseModel):
|
|
|
55
87
|
title: str = Field(alias="marketName")
|
|
56
88
|
outcomes: t.Sequence[OutcomeStr]
|
|
57
89
|
wrapped_tokens: list[HexAddress] = Field(alias="wrappedTokens")
|
|
58
|
-
parent_outcome: int = Field(
|
|
59
|
-
|
|
60
|
-
alias="parentMarket", default=None
|
|
90
|
+
parent_outcome: int = Field(
|
|
91
|
+
alias="parentOutcome", description="It comes as 0 from non-conditioned markets."
|
|
61
92
|
)
|
|
93
|
+
parent_market: t.Optional["SeerMarket"] = Field(alias="parentMarket", default=None)
|
|
94
|
+
template_id: int = Field(alias="templateId")
|
|
62
95
|
collateral_token: HexAddress = Field(alias="collateralToken")
|
|
63
96
|
condition_id: HexBytes = Field(alias="conditionId")
|
|
64
97
|
opening_ts: int = Field(alias="openingTs")
|
|
@@ -67,6 +100,8 @@ class SeerMarket(BaseModel):
|
|
|
67
100
|
payout_reported: bool = Field(alias="payoutReported")
|
|
68
101
|
payout_numerators: list[int] = Field(alias="payoutNumerators")
|
|
69
102
|
outcomes_supply: int = Field(alias="outcomesSupply")
|
|
103
|
+
upper_bound: SeerNormalizedWei = Field(alias="upperBound", default=None)
|
|
104
|
+
lower_bound: SeerNormalizedWei = Field(alias="lowerBound", default=None)
|
|
70
105
|
|
|
71
106
|
@property
|
|
72
107
|
def has_valid_answer(self) -> bool:
|
|
@@ -105,11 +140,6 @@ class SeerMarket(BaseModel):
|
|
|
105
140
|
for token in self.wrapped_tokens
|
|
106
141
|
]
|
|
107
142
|
|
|
108
|
-
@property
|
|
109
|
-
def is_binary(self) -> bool:
|
|
110
|
-
# 3 because Seer has also third, `Invalid` outcome.
|
|
111
|
-
return len(self.outcomes) == 3
|
|
112
|
-
|
|
113
143
|
@property
|
|
114
144
|
def collateral_token_contract_address_checksummed(self) -> ChecksumAddress:
|
|
115
145
|
return Web3.to_checksum_address(self.collateral_token)
|
|
@@ -122,14 +152,78 @@ class SeerMarket(BaseModel):
|
|
|
122
152
|
def created_time(self) -> DatetimeUTC:
|
|
123
153
|
return DatetimeUTC.to_datetime_utc(self.block_timestamp)
|
|
124
154
|
|
|
155
|
+
@computed_field # type: ignore[prop-decorator]
|
|
125
156
|
@property
|
|
126
157
|
def url(self) -> str:
|
|
127
158
|
chain_id = RPCConfig().chain_id
|
|
128
|
-
return urljoin(SEER_BASE_URL, f"markets/{chain_id}/{self.id.
|
|
159
|
+
return urljoin(SEER_BASE_URL, f"markets/{chain_id}/{self.id.to_0x_hex()}")
|
|
160
|
+
|
|
161
|
+
|
|
162
|
+
class SeerMarketWithQuestions(SeerMarket):
|
|
163
|
+
questions: list[SeerMarketQuestions]
|
|
164
|
+
|
|
165
|
+
|
|
166
|
+
class ExactInputSingleParams(BaseModel):
|
|
167
|
+
# from https://gnosisscan.io/address/0xffb643e73f280b97809a8b41f7232ab401a04ee1#code
|
|
168
|
+
model_config = ConfigDict(populate_by_name=True)
|
|
169
|
+
token_in: ChecksumAddress = Field(alias="tokenIn")
|
|
170
|
+
token_out: ChecksumAddress = Field(alias="tokenOut")
|
|
171
|
+
recipient: ChecksumAddress
|
|
172
|
+
deadline: int = Field(
|
|
173
|
+
default_factory=lambda: int((utcnow() + timedelta(minutes=10)).timestamp())
|
|
174
|
+
)
|
|
175
|
+
amount_in: Wei = Field(alias="amountIn")
|
|
176
|
+
amount_out_minimum: Wei = Field(alias="amountOutMinimum")
|
|
177
|
+
limit_sqrt_price: Wei = Field(
|
|
178
|
+
alias="limitSqrtPrice", default_factory=lambda: Wei(0)
|
|
179
|
+
) # 0 for convenience, we also don't expect major price shifts
|
|
129
180
|
|
|
130
181
|
|
|
131
|
-
class
|
|
182
|
+
class QuoteExactInputSingleParams(BaseModel):
|
|
183
|
+
# from https://gnosisscan.io/address/0xcBaD9FDf0D2814659Eb26f600EFDeAF005Eda0F7#writeContract
|
|
132
184
|
model_config = ConfigDict(populate_by_name=True)
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
185
|
+
token_in: ChecksumAddress = Field(alias="tokenIn")
|
|
186
|
+
token_out: ChecksumAddress = Field(alias="tokenOut")
|
|
187
|
+
amount_in: Wei = Field(alias="amountIn")
|
|
188
|
+
limit_sqrt_price: Wei = Field(
|
|
189
|
+
alias="limitSqrtPrice", default_factory=lambda: Wei(0)
|
|
190
|
+
) # 0 for convenience, we also don't expect major price shifts
|
|
191
|
+
|
|
192
|
+
|
|
193
|
+
class SeerTransactionType(str, Enum):
|
|
194
|
+
SWAP = "swap"
|
|
195
|
+
SPLIT = "split"
|
|
196
|
+
|
|
197
|
+
|
|
198
|
+
class SeerTransaction(BaseModel):
|
|
199
|
+
model_config = ConfigDict(populate_by_name=True)
|
|
200
|
+
|
|
201
|
+
market_name: str = Field(alias="marketName")
|
|
202
|
+
market_id: HexBytes = Field(alias="marketId")
|
|
203
|
+
type: SeerTransactionType
|
|
204
|
+
block_number: int = Field(alias="blockNumber")
|
|
205
|
+
transaction_hash: HexBytes = Field(alias="transactionHash")
|
|
206
|
+
collateral: HexAddress
|
|
207
|
+
collateral_symbol: str = Field(alias="collateralSymbol")
|
|
208
|
+
|
|
209
|
+
token_in: HexAddress = Field(alias="tokenIn")
|
|
210
|
+
token_out: HexAddress = Field(alias="tokenOut")
|
|
211
|
+
amount_in: Wei = Field(alias="amountIn")
|
|
212
|
+
amount_out: Wei = Field(alias="amountOut")
|
|
213
|
+
token_in_symbol: str = Field(alias="tokenInSymbol")
|
|
214
|
+
token_out_symbol: str = Field(alias="tokenOutSymbol")
|
|
215
|
+
timestamp: int | None = None
|
|
216
|
+
|
|
217
|
+
amount: Wei | None = None
|
|
218
|
+
|
|
219
|
+
@property
|
|
220
|
+
def timestamp_dt(self) -> DatetimeUTC | None:
|
|
221
|
+
return DatetimeUTC.to_datetime_utc(self.timestamp) if self.timestamp else None
|
|
222
|
+
|
|
223
|
+
@property
|
|
224
|
+
def token_in_checksum(self) -> ChecksumAddress:
|
|
225
|
+
return Web3.to_checksum_address(self.token_in)
|
|
226
|
+
|
|
227
|
+
@property
|
|
228
|
+
def token_out_checksum(self) -> ChecksumAddress:
|
|
229
|
+
return Web3.to_checksum_address(self.token_out)
|
|
@@ -1,46 +1,38 @@
|
|
|
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
|
|
|
5
|
+
from prediction_market_agent_tooling.deploy.constants import is_invalid_outcome
|
|
6
6
|
from prediction_market_agent_tooling.gtypes import (
|
|
7
7
|
ChecksumAddress,
|
|
8
8
|
CollateralToken,
|
|
9
9
|
HexAddress,
|
|
10
10
|
OutcomeStr,
|
|
11
|
+
OutcomeToken,
|
|
11
12
|
Probability,
|
|
13
|
+
Wei,
|
|
12
14
|
)
|
|
13
15
|
from prediction_market_agent_tooling.loggers import logger
|
|
14
16
|
from prediction_market_agent_tooling.markets.seer.data_models import SeerMarket
|
|
17
|
+
from prediction_market_agent_tooling.markets.seer.exceptions import (
|
|
18
|
+
PriceCalculationError,
|
|
19
|
+
)
|
|
20
|
+
from prediction_market_agent_tooling.markets.seer.seer_contracts import (
|
|
21
|
+
QuoteExactInputSingleParams,
|
|
22
|
+
SwaprQuoterContract,
|
|
23
|
+
)
|
|
15
24
|
from prediction_market_agent_tooling.markets.seer.seer_subgraph_handler import (
|
|
16
25
|
SeerSubgraphHandler,
|
|
17
26
|
)
|
|
18
|
-
from prediction_market_agent_tooling.markets.seer.subgraph_data_models import SeerPool
|
|
19
27
|
from prediction_market_agent_tooling.tools.cow.cow_order import (
|
|
20
28
|
get_buy_token_amount_else_raise,
|
|
21
29
|
)
|
|
22
30
|
from prediction_market_agent_tooling.tools.hexbytes_custom import HexBytes
|
|
23
31
|
|
|
24
32
|
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
collateral_exchange_amount: CollateralToken | None = None,
|
|
29
|
-
) -> str:
|
|
30
|
-
"""
|
|
31
|
-
Generate a unique cache key based on a token address and optional collateral token.
|
|
32
|
-
"""
|
|
33
|
-
|
|
34
|
-
if collateral_exchange_amount is None:
|
|
35
|
-
return f"{token}-no_collateral"
|
|
36
|
-
|
|
37
|
-
return "-".join(
|
|
38
|
-
[
|
|
39
|
-
token,
|
|
40
|
-
collateral_exchange_amount.symbol,
|
|
41
|
-
str(collateral_exchange_amount.value),
|
|
42
|
-
]
|
|
43
|
-
)
|
|
33
|
+
class Prices(BaseModel):
|
|
34
|
+
priceOfCollateralInAskingToken: CollateralToken
|
|
35
|
+
priceOfAskingTokenInCollateral: CollateralToken
|
|
44
36
|
|
|
45
37
|
|
|
46
38
|
class PriceManager:
|
|
@@ -61,20 +53,20 @@ class PriceManager:
|
|
|
61
53
|
price_diff_pct = abs(old_price - normalized_price) / max(old_price, 0.01)
|
|
62
54
|
if price_diff_pct > max_price_diff:
|
|
63
55
|
logger.info(
|
|
64
|
-
f"{price_diff_pct=} larger than {max_price_diff=} for seer market {self.seer_market.id.
|
|
56
|
+
f"{price_diff_pct=} larger than {max_price_diff=} for seer market {self.seer_market.id.to_0x_hex()} "
|
|
65
57
|
)
|
|
66
58
|
|
|
67
|
-
|
|
68
|
-
|
|
59
|
+
def get_price_for_token(self, token: ChecksumAddress) -> CollateralToken | None:
|
|
60
|
+
return self.get_amount_of_token_in_collateral(token, OutcomeToken(1))
|
|
61
|
+
|
|
62
|
+
@cached(TTLCache(maxsize=100, ttl=5 * 60))
|
|
63
|
+
def get_amount_of_collateral_in_token(
|
|
69
64
|
self,
|
|
70
65
|
token: ChecksumAddress,
|
|
71
|
-
collateral_exchange_amount: CollateralToken
|
|
72
|
-
) ->
|
|
73
|
-
|
|
74
|
-
collateral_exchange_amount
|
|
75
|
-
if collateral_exchange_amount is not None
|
|
76
|
-
else CollateralToken(1)
|
|
77
|
-
)
|
|
66
|
+
collateral_exchange_amount: CollateralToken,
|
|
67
|
+
) -> OutcomeToken | None:
|
|
68
|
+
if token == self.seer_market.collateral_token_contract_address_checksummed:
|
|
69
|
+
return OutcomeToken(collateral_exchange_amount.value)
|
|
78
70
|
|
|
79
71
|
try:
|
|
80
72
|
buy_token_amount = get_buy_token_amount_else_raise(
|
|
@@ -82,23 +74,55 @@ class PriceManager:
|
|
|
82
74
|
sell_token=self.seer_market.collateral_token_contract_address_checksummed,
|
|
83
75
|
buy_token=token,
|
|
84
76
|
)
|
|
85
|
-
|
|
86
|
-
return CollateralToken(price)
|
|
77
|
+
return OutcomeToken.from_token(buy_token_amount.as_token)
|
|
87
78
|
|
|
88
79
|
except Exception as e:
|
|
89
80
|
logger.warning(
|
|
90
81
|
f"Could not get quote for {token=} from Cow, exception {e=}. Falling back to pools. "
|
|
91
82
|
)
|
|
92
|
-
|
|
83
|
+
quote = self.get_swapr_input_quote(
|
|
84
|
+
input_token=self.seer_market.collateral_token_contract_address_checksummed,
|
|
85
|
+
output_token=token,
|
|
86
|
+
input_amount=collateral_exchange_amount.as_wei,
|
|
87
|
+
)
|
|
88
|
+
return OutcomeToken.from_token(quote.as_token)
|
|
93
89
|
|
|
94
|
-
@
|
|
95
|
-
def
|
|
96
|
-
|
|
90
|
+
@cached(TTLCache(maxsize=100, ttl=5 * 60))
|
|
91
|
+
def get_amount_of_token_in_collateral(
|
|
92
|
+
self,
|
|
93
|
+
token: ChecksumAddress,
|
|
94
|
+
token_exchange_amount: OutcomeToken,
|
|
95
|
+
) -> CollateralToken | None:
|
|
96
|
+
if token == self.seer_market.collateral_token_contract_address_checksummed:
|
|
97
|
+
return token_exchange_amount.as_token
|
|
98
|
+
|
|
99
|
+
try:
|
|
100
|
+
buy_collateral_amount = get_buy_token_amount_else_raise(
|
|
101
|
+
sell_amount=token_exchange_amount.as_outcome_wei.as_wei,
|
|
102
|
+
sell_token=token,
|
|
103
|
+
buy_token=self.seer_market.collateral_token_contract_address_checksummed,
|
|
104
|
+
)
|
|
105
|
+
return buy_collateral_amount.as_token
|
|
106
|
+
|
|
107
|
+
except Exception as e:
|
|
108
|
+
logger.warning(
|
|
109
|
+
f"Could not get quote for {token=} from Cow, exception {e=}. Falling back to pools. "
|
|
110
|
+
)
|
|
111
|
+
quote = self.get_swapr_input_quote(
|
|
112
|
+
input_token=token,
|
|
113
|
+
output_token=self.seer_market.collateral_token_contract_address_checksummed,
|
|
114
|
+
input_amount=token_exchange_amount.as_outcome_wei.as_wei,
|
|
115
|
+
)
|
|
116
|
+
return quote.as_token
|
|
97
117
|
|
|
98
118
|
def get_token_price_from_pools(
|
|
99
119
|
self,
|
|
100
120
|
token: ChecksumAddress,
|
|
101
|
-
) ->
|
|
121
|
+
) -> Prices | None:
|
|
122
|
+
"""
|
|
123
|
+
Although this might come handy,
|
|
124
|
+
consider using `get_amount_of_collateral_in_token` or `get_amount_of_token_in_collateral` to have an exact quote.
|
|
125
|
+
"""
|
|
102
126
|
pool = SeerSubgraphHandler().get_pool_by_token(
|
|
103
127
|
token_address=token,
|
|
104
128
|
collateral_address=self.seer_market.collateral_token_contract_address_checksummed,
|
|
@@ -108,15 +132,24 @@ class PriceManager:
|
|
|
108
132
|
logger.warning(f"Could not find a pool for {token=}")
|
|
109
133
|
return None
|
|
110
134
|
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
135
|
+
if (
|
|
136
|
+
Web3.to_checksum_address(pool.token0.id)
|
|
137
|
+
== self.seer_market.collateral_token_contract_address_checksummed
|
|
138
|
+
):
|
|
139
|
+
price_coll_in_asking = (
|
|
140
|
+
pool.token1Price
|
|
141
|
+
) # how many outcome tokens per 1 collateral
|
|
142
|
+
price_asking_in_coll = (
|
|
143
|
+
pool.token0Price
|
|
144
|
+
) # how many collateral tokens per 1 outcome
|
|
145
|
+
else:
|
|
146
|
+
price_coll_in_asking = pool.token0Price
|
|
147
|
+
price_asking_in_coll = pool.token1Price
|
|
148
|
+
|
|
149
|
+
return Prices(
|
|
150
|
+
priceOfCollateralInAskingToken=price_coll_in_asking,
|
|
151
|
+
priceOfAskingTokenInCollateral=price_asking_in_coll,
|
|
118
152
|
)
|
|
119
|
-
return price
|
|
120
153
|
|
|
121
154
|
def build_probability_map(self) -> dict[OutcomeStr, Probability]:
|
|
122
155
|
# Inspired by https://github.com/seer-pm/demo/blob/ca682153a6b4d4dd3dcc4ad8bdcbe32202fc8fe7/web/src/hooks/useMarketOdds.ts#L15
|
|
@@ -128,7 +161,7 @@ class PriceManager:
|
|
|
128
161
|
)
|
|
129
162
|
# It's okay if invalid (last) outcome has price 0, but not the other outcomes.
|
|
130
163
|
if price is None and idx != len(self.seer_market.wrapped_tokens) - 1:
|
|
131
|
-
raise
|
|
164
|
+
raise PriceCalculationError(
|
|
132
165
|
f"Couldn't get price for {wrapped_token} for market {self.seer_market.url}."
|
|
133
166
|
)
|
|
134
167
|
price_data[wrapped_token] = (
|
|
@@ -142,7 +175,7 @@ class PriceManager:
|
|
|
142
175
|
sum(price_data.values(), start=CollateralToken.zero())
|
|
143
176
|
== CollateralToken.zero()
|
|
144
177
|
):
|
|
145
|
-
raise
|
|
178
|
+
raise PriceCalculationError(
|
|
146
179
|
f"All prices for market {self.seer_market.url} are zero. This shouldn't happen."
|
|
147
180
|
)
|
|
148
181
|
|
|
@@ -157,6 +190,88 @@ class PriceManager:
|
|
|
157
190
|
outcome = self.seer_market.outcomes[
|
|
158
191
|
self.seer_market.wrapped_tokens.index(outcome_token)
|
|
159
192
|
]
|
|
160
|
-
normalized_prices[
|
|
193
|
+
normalized_prices[outcome] = new_price
|
|
161
194
|
|
|
162
195
|
return normalized_prices
|
|
196
|
+
|
|
197
|
+
def build_initial_probs_from_pool(
|
|
198
|
+
self, model: SeerMarket, wrapped_tokens: list[ChecksumAddress]
|
|
199
|
+
) -> tuple[dict[OutcomeStr, Probability], dict[OutcomeStr, OutcomeToken]]:
|
|
200
|
+
"""
|
|
201
|
+
Builds a map of outcome to probability and outcome token pool.
|
|
202
|
+
"""
|
|
203
|
+
probability_map = {}
|
|
204
|
+
outcome_token_pool = {}
|
|
205
|
+
wrapped_tokens_with_supply = [
|
|
206
|
+
(
|
|
207
|
+
token,
|
|
208
|
+
SeerSubgraphHandler().get_pool_by_token(
|
|
209
|
+
token, model.collateral_token_contract_address_checksummed
|
|
210
|
+
),
|
|
211
|
+
)
|
|
212
|
+
for token in wrapped_tokens
|
|
213
|
+
]
|
|
214
|
+
wrapped_tokens_with_supply = [
|
|
215
|
+
(token, pool)
|
|
216
|
+
for token, pool in wrapped_tokens_with_supply
|
|
217
|
+
if pool is not None
|
|
218
|
+
]
|
|
219
|
+
|
|
220
|
+
for token, pool in wrapped_tokens_with_supply:
|
|
221
|
+
if pool is None or pool.token1.id is None or pool.token0.id is None:
|
|
222
|
+
continue
|
|
223
|
+
if HexBytes(token) == HexBytes(pool.token1.id):
|
|
224
|
+
outcome_token_pool[
|
|
225
|
+
OutcomeStr(model.outcomes[wrapped_tokens.index(token)])
|
|
226
|
+
] = (
|
|
227
|
+
OutcomeToken(pool.totalValueLockedToken0)
|
|
228
|
+
if pool.totalValueLockedToken0 is not None
|
|
229
|
+
else OutcomeToken(0)
|
|
230
|
+
)
|
|
231
|
+
probability_map[
|
|
232
|
+
OutcomeStr(model.outcomes[wrapped_tokens.index(token)])
|
|
233
|
+
] = Probability(pool.token0Price.value)
|
|
234
|
+
else:
|
|
235
|
+
outcome_token_pool[
|
|
236
|
+
OutcomeStr(model.outcomes[wrapped_tokens.index(token)])
|
|
237
|
+
] = (
|
|
238
|
+
OutcomeToken(pool.totalValueLockedToken1)
|
|
239
|
+
if pool.totalValueLockedToken1 is not None
|
|
240
|
+
else OutcomeToken(0)
|
|
241
|
+
)
|
|
242
|
+
probability_map[
|
|
243
|
+
OutcomeStr(model.outcomes[wrapped_tokens.index(token)])
|
|
244
|
+
] = Probability(pool.token1Price.value)
|
|
245
|
+
|
|
246
|
+
for outcome in model.outcomes:
|
|
247
|
+
if outcome not in outcome_token_pool:
|
|
248
|
+
outcome_token_pool[outcome] = OutcomeToken(0)
|
|
249
|
+
logger.warning(
|
|
250
|
+
f"Outcome {outcome} not found in outcome_token_pool for market {self.seer_market.url}."
|
|
251
|
+
)
|
|
252
|
+
if outcome not in probability_map:
|
|
253
|
+
if not is_invalid_outcome(outcome):
|
|
254
|
+
raise PriceCalculationError(
|
|
255
|
+
f"Couldn't get probability for {outcome} for market {self.seer_market.url}."
|
|
256
|
+
)
|
|
257
|
+
else:
|
|
258
|
+
probability_map[outcome] = Probability(0)
|
|
259
|
+
return probability_map, outcome_token_pool
|
|
260
|
+
|
|
261
|
+
def get_swapr_input_quote(
|
|
262
|
+
self,
|
|
263
|
+
input_token: ChecksumAddress,
|
|
264
|
+
output_token: ChecksumAddress,
|
|
265
|
+
input_amount: Wei,
|
|
266
|
+
web3: Web3 | None = None,
|
|
267
|
+
) -> Wei: # Not marked as OutcomeWei, but this works for both buying and selling.
|
|
268
|
+
quoter = SwaprQuoterContract()
|
|
269
|
+
amount_out, _ = quoter.quote_exact_input_single(
|
|
270
|
+
QuoteExactInputSingleParams(
|
|
271
|
+
token_in=input_token,
|
|
272
|
+
token_out=output_token,
|
|
273
|
+
amount_in=input_amount,
|
|
274
|
+
),
|
|
275
|
+
web3=web3,
|
|
276
|
+
)
|
|
277
|
+
return amount_out
|