prediction-market-agent-tooling 0.62.1__py3-none-any.whl → 0.63.0.dev499__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/betting_strategy.py +21 -5
- prediction_market_agent_tooling/markets/agent_market.py +3 -1
- prediction_market_agent_tooling/markets/blockchain_utils.py +6 -2
- prediction_market_agent_tooling/markets/omen/omen.py +1 -0
- prediction_market_agent_tooling/markets/seer/data_models.py +4 -65
- prediction_market_agent_tooling/markets/seer/price_manager.py +122 -0
- prediction_market_agent_tooling/markets/seer/seer.py +103 -45
- prediction_market_agent_tooling/markets/seer/seer_contracts.py +4 -4
- prediction_market_agent_tooling/markets/seer/seer_subgraph_handler.py +47 -18
- prediction_market_agent_tooling/markets/seer/subgraph_data_models.py +57 -0
- prediction_market_agent_tooling/tools/contract.py +11 -0
- prediction_market_agent_tooling/tools/cow/cow_order.py +53 -19
- prediction_market_agent_tooling/tools/tokens/auto_deposit.py +1 -0
- prediction_market_agent_tooling/tools/tokens/token_utils.py +4 -3
- prediction_market_agent_tooling/tools/tokens/usd.py +7 -6
- {prediction_market_agent_tooling-0.62.1.dist-info → prediction_market_agent_tooling-0.63.0.dev499.dist-info}/METADATA +1 -1
- {prediction_market_agent_tooling-0.62.1.dist-info → prediction_market_agent_tooling-0.63.0.dev499.dist-info}/RECORD +20 -19
- prediction_market_agent_tooling/tools/cow/cow_manager.py +0 -112
- {prediction_market_agent_tooling-0.62.1.dist-info → prediction_market_agent_tooling-0.63.0.dev499.dist-info}/LICENSE +0 -0
- {prediction_market_agent_tooling-0.62.1.dist-info → prediction_market_agent_tooling-0.63.0.dev499.dist-info}/WHEEL +0 -0
- {prediction_market_agent_tooling-0.62.1.dist-info → prediction_market_agent_tooling-0.63.0.dev499.dist-info}/entry_points.txt +0 -0
@@ -47,23 +47,39 @@ class BettingStrategy(ABC):
|
|
47
47
|
@staticmethod
|
48
48
|
def assert_buy_trade_wont_be_guaranteed_loss(
|
49
49
|
market: AgentMarket, trades: list[Trade]
|
50
|
-
) ->
|
50
|
+
) -> list[Trade]:
|
51
|
+
clean_trades = []
|
51
52
|
for trade in trades:
|
52
53
|
if trade.trade_type == TradeType.BUY:
|
53
54
|
outcome_tokens_to_get = market.get_buy_token_amount(
|
54
55
|
trade.amount, trade.outcome
|
55
56
|
)
|
57
|
+
|
58
|
+
if not outcome_tokens_to_get:
|
59
|
+
logger.info(
|
60
|
+
f"Could not determine buy_token_amount for trade {trade}. Skipping trade."
|
61
|
+
)
|
62
|
+
continue
|
63
|
+
|
56
64
|
outcome_tokens_to_get_in_usd = market.get_token_in_usd(
|
57
65
|
outcome_tokens_to_get.as_token
|
58
66
|
)
|
67
|
+
|
59
68
|
if outcome_tokens_to_get_in_usd <= trade.amount:
|
60
69
|
raise GuaranteedLossError(
|
61
|
-
f"Trade {trade=} would result in guaranteed loss by getting only {outcome_tokens_to_get=}."
|
70
|
+
f"Trade {trade=} would result in guaranteed loss by getting only {outcome_tokens_to_get=}. Halting execution."
|
62
71
|
)
|
63
72
|
|
73
|
+
clean_trades.append(trade)
|
74
|
+
|
75
|
+
return clean_trades
|
76
|
+
|
64
77
|
@staticmethod
|
65
|
-
def
|
66
|
-
BettingStrategy.assert_buy_trade_wont_be_guaranteed_loss(
|
78
|
+
def filter_trades(market: AgentMarket, trades: list[Trade]) -> list[Trade]:
|
79
|
+
trades = BettingStrategy.assert_buy_trade_wont_be_guaranteed_loss(
|
80
|
+
market, trades
|
81
|
+
)
|
82
|
+
return trades
|
67
83
|
|
68
84
|
def _build_rebalance_trades_from_positions(
|
69
85
|
self,
|
@@ -109,7 +125,7 @@ class BettingStrategy(ABC):
|
|
109
125
|
trades.sort(key=lambda t: t.trade_type == TradeType.SELL)
|
110
126
|
|
111
127
|
# Run some sanity checks to not place unreasonable bets.
|
112
|
-
BettingStrategy.
|
128
|
+
trades = BettingStrategy.filter_trades(market, trades)
|
113
129
|
|
114
130
|
return trades
|
115
131
|
|
@@ -4,6 +4,7 @@ from enum import Enum
|
|
4
4
|
from eth_typing import ChecksumAddress
|
5
5
|
from pydantic import BaseModel, field_validator, model_validator
|
6
6
|
from pydantic_core.core_schema import FieldValidationInfo
|
7
|
+
from web3 import Web3
|
7
8
|
|
8
9
|
from prediction_market_agent_tooling.config import APIKeys
|
9
10
|
from prediction_market_agent_tooling.gtypes import (
|
@@ -235,7 +236,7 @@ class AgentMarket(BaseModel):
|
|
235
236
|
|
236
237
|
def get_buy_token_amount(
|
237
238
|
self, bet_amount: USD | CollateralToken, direction: bool
|
238
|
-
) -> OutcomeToken:
|
239
|
+
) -> OutcomeToken | None:
|
239
240
|
raise NotImplementedError("Subclasses must implement this method")
|
240
241
|
|
241
242
|
def sell_tokens(self, outcome: bool, amount: USD | OutcomeToken) -> str:
|
@@ -293,6 +294,7 @@ class AgentMarket(BaseModel):
|
|
293
294
|
traded_market: ProcessedTradedMarket | None,
|
294
295
|
keys: APIKeys,
|
295
296
|
agent_name: str,
|
297
|
+
web3: Web3 | None = None,
|
296
298
|
) -> None:
|
297
299
|
"""
|
298
300
|
If market allows to upload trades somewhere, implement it in this method.
|
@@ -22,10 +22,11 @@ def store_trades(
|
|
22
22
|
traded_market: ProcessedTradedMarket | None,
|
23
23
|
keys: APIKeys,
|
24
24
|
agent_name: str,
|
25
|
+
web3: Web3 | None = None,
|
25
26
|
) -> None:
|
26
27
|
if traded_market is None:
|
27
28
|
logger.warning(f"No prediction for market {market_id}, not storing anything.")
|
28
|
-
return
|
29
|
+
return None
|
29
30
|
|
30
31
|
reasoning = traded_market.answer.reasoning if traded_market.answer.reasoning else ""
|
31
32
|
|
@@ -37,8 +38,10 @@ def store_trades(
|
|
37
38
|
)
|
38
39
|
ipfs_hash_decoded = ipfscidv0_to_byte32(ipfs_hash)
|
39
40
|
|
41
|
+
# tx_hashes must be list of bytes32 (see Solidity contract).
|
42
|
+
# For regular tx hashes that's fine, but for other types of IDs we take the first 32 bytes (orderDigest).
|
40
43
|
tx_hashes = [
|
41
|
-
HexBytes(HexStr(i.id)) for i in traded_market.trades if i.id is not None
|
44
|
+
HexBytes(HexStr(i.id[:32])) for i in traded_market.trades if i.id is not None
|
42
45
|
]
|
43
46
|
prediction = ContractPrediction(
|
44
47
|
publisher=keys.bet_from_address,
|
@@ -50,6 +53,7 @@ def store_trades(
|
|
50
53
|
api_keys=keys,
|
51
54
|
market_address=Web3.to_checksum_address(market_id),
|
52
55
|
prediction=prediction,
|
56
|
+
web3=web3,
|
53
57
|
)
|
54
58
|
logger.info(
|
55
59
|
f"Added prediction to market {market_id}. - receipt {tx_receipt['transactionHash'].hex()}."
|
@@ -10,16 +10,15 @@ from web3.constants import ADDRESS_ZERO
|
|
10
10
|
from prediction_market_agent_tooling.config import RPCConfig
|
11
11
|
from prediction_market_agent_tooling.gtypes import (
|
12
12
|
ChecksumAddress,
|
13
|
-
CollateralToken,
|
14
13
|
HexAddress,
|
15
14
|
HexBytes,
|
16
15
|
OutcomeStr,
|
17
|
-
Probability,
|
18
16
|
Web3Wei,
|
19
17
|
)
|
20
|
-
from prediction_market_agent_tooling.loggers import logger
|
21
18
|
from prediction_market_agent_tooling.markets.data_models import Resolution
|
22
|
-
from prediction_market_agent_tooling.
|
19
|
+
from prediction_market_agent_tooling.markets.seer.subgraph_data_models import (
|
20
|
+
SeerParentMarket,
|
21
|
+
)
|
23
22
|
from prediction_market_agent_tooling.tools.datetime_utc import DatetimeUTC
|
24
23
|
|
25
24
|
|
@@ -84,10 +83,6 @@ class SeerOutcomeEnum(str, Enum):
|
|
84
83
|
raise ValueError(f"Unknown outcome: {self}")
|
85
84
|
|
86
85
|
|
87
|
-
class SeerParentMarket(BaseModel):
|
88
|
-
id: HexBytes
|
89
|
-
|
90
|
-
|
91
86
|
SEER_BASE_URL = "https://app.seer.pm"
|
92
87
|
|
93
88
|
|
@@ -110,6 +105,7 @@ class SeerMarket(BaseModel):
|
|
110
105
|
has_answers: bool | None = Field(alias="hasAnswers")
|
111
106
|
payout_reported: bool = Field(alias="payoutReported")
|
112
107
|
payout_numerators: list[int] = Field(alias="payoutNumerators")
|
108
|
+
outcomes_supply: int = Field(alias="outcomesSupply")
|
113
109
|
|
114
110
|
@property
|
115
111
|
def has_valid_answer(self) -> bool:
|
@@ -186,64 +182,7 @@ class SeerMarket(BaseModel):
|
|
186
182
|
def created_time(self) -> DatetimeUTC:
|
187
183
|
return DatetimeUTC.to_datetime_utc(self.block_timestamp)
|
188
184
|
|
189
|
-
@property
|
190
|
-
def current_p_yes(self) -> Probability:
|
191
|
-
price_data = {}
|
192
|
-
for idx in range(len(self.outcomes)):
|
193
|
-
wrapped_token = self.wrapped_tokens[idx]
|
194
|
-
price = self._get_price_for_token(
|
195
|
-
token=Web3.to_checksum_address(wrapped_token)
|
196
|
-
)
|
197
|
-
price_data[idx] = price
|
198
|
-
|
199
|
-
if sum(price_data.values()) == 0:
|
200
|
-
logger.warning(
|
201
|
-
f"Could not get p_yes for market {self.id.hex()}, all price quotes are 0."
|
202
|
-
)
|
203
|
-
return Probability(0)
|
204
|
-
|
205
|
-
yes_idx = self.outcome_as_enums[SeerOutcomeEnum.YES]
|
206
|
-
price_yes = price_data[yes_idx] / sum(price_data.values())
|
207
|
-
return Probability(price_yes)
|
208
|
-
|
209
|
-
def _get_price_for_token(self, token: ChecksumAddress) -> float:
|
210
|
-
collateral_exchange_amount = CollateralToken(1).as_wei
|
211
|
-
try:
|
212
|
-
quote = CowManager().get_quote(
|
213
|
-
collateral_token=self.collateral_token_contract_address_checksummed,
|
214
|
-
buy_token=token,
|
215
|
-
sell_amount=collateral_exchange_amount,
|
216
|
-
)
|
217
|
-
except Exception as e:
|
218
|
-
logger.warning(f"Could not get quote for {token=}, returning price 0. {e=}")
|
219
|
-
return 0
|
220
|
-
|
221
|
-
return collateral_exchange_amount.value / float(quote.quote.buyAmount.root)
|
222
|
-
|
223
185
|
@property
|
224
186
|
def url(self) -> str:
|
225
187
|
chain_id = RPCConfig().chain_id
|
226
188
|
return urljoin(SEER_BASE_URL, f"markets/{chain_id}/{self.id.hex()}")
|
227
|
-
|
228
|
-
|
229
|
-
class SeerToken(BaseModel):
|
230
|
-
id: HexBytes
|
231
|
-
name: str
|
232
|
-
symbol: str
|
233
|
-
|
234
|
-
|
235
|
-
class SeerPool(BaseModel):
|
236
|
-
model_config = ConfigDict(populate_by_name=True)
|
237
|
-
id: HexBytes
|
238
|
-
liquidity: int
|
239
|
-
token0: SeerToken
|
240
|
-
token1: SeerToken
|
241
|
-
|
242
|
-
|
243
|
-
class NewMarketEvent(BaseModel):
|
244
|
-
market: HexAddress
|
245
|
-
marketName: str
|
246
|
-
parentMarket: HexAddress
|
247
|
-
conditionId: HexBytes
|
248
|
-
questionId: HexBytes
|
249
|
-
questionsIds: list[HexBytes]
|
@@ -0,0 +1,122 @@
|
|
1
|
+
from web3 import Web3
|
2
|
+
|
3
|
+
from prediction_market_agent_tooling.gtypes import (
|
4
|
+
ChecksumAddress,
|
5
|
+
Probability,
|
6
|
+
Wei,
|
7
|
+
CollateralToken,
|
8
|
+
)
|
9
|
+
from prediction_market_agent_tooling.loggers import logger
|
10
|
+
from prediction_market_agent_tooling.markets.seer.data_models import (
|
11
|
+
SeerMarket,
|
12
|
+
SeerOutcomeEnum,
|
13
|
+
)
|
14
|
+
from prediction_market_agent_tooling.markets.seer.seer_subgraph_handler import (
|
15
|
+
SeerSubgraphHandler,
|
16
|
+
)
|
17
|
+
from prediction_market_agent_tooling.markets.seer.subgraph_data_models import SeerPool
|
18
|
+
from prediction_market_agent_tooling.tools.cow.cow_order import get_quote
|
19
|
+
from prediction_market_agent_tooling.tools.hexbytes_custom import HexBytes
|
20
|
+
from prediction_market_agent_tooling.tools.utils import check_not_none
|
21
|
+
|
22
|
+
|
23
|
+
class PriceManager:
|
24
|
+
def __init__(self, seer_market: SeerMarket, seer_subgraph: SeerSubgraphHandler):
|
25
|
+
self.seer_market = seer_market
|
26
|
+
self.seer_subgraph = seer_subgraph
|
27
|
+
|
28
|
+
@staticmethod
|
29
|
+
def build(market_id: HexBytes) -> "PriceManager":
|
30
|
+
s = SeerSubgraphHandler()
|
31
|
+
market = s.get_market_by_id(market_id=market_id)
|
32
|
+
return PriceManager(seer_market=market, seer_subgraph=s)
|
33
|
+
|
34
|
+
def _log_track_price_normalization_diff(
|
35
|
+
self, old_price: float, normalized_price: float, max_price_diff: float = 0.05
|
36
|
+
) -> None:
|
37
|
+
price_diff_pct = abs(old_price - normalized_price) / old_price
|
38
|
+
if price_diff_pct > max_price_diff:
|
39
|
+
logger.info(
|
40
|
+
f"{price_diff_pct=} larger than {max_price_diff=} for seer market {self.seer_market.id.hex()} "
|
41
|
+
)
|
42
|
+
|
43
|
+
def current_p_yes(self) -> Probability | None:
|
44
|
+
# Inspired by https://github.com/seer-pm/demo/blob/ca682153a6b4d4dd3dcc4ad8bdcbe32202fc8fe7/web/src/hooks/useMarketOdds.ts#L15
|
45
|
+
price_data = {}
|
46
|
+
for idx, wrapped_token in enumerate(self.seer_market.wrapped_tokens):
|
47
|
+
price = self.get_price_for_token(
|
48
|
+
token=Web3.to_checksum_address(wrapped_token),
|
49
|
+
)
|
50
|
+
|
51
|
+
price_data[idx] = price
|
52
|
+
|
53
|
+
price_yes = price_data[self.seer_market.outcome_as_enums[SeerOutcomeEnum.YES]]
|
54
|
+
price_no = price_data[self.seer_market.outcome_as_enums[SeerOutcomeEnum.NO]]
|
55
|
+
|
56
|
+
# We only return a probability if we have both price_yes and price_no, since we could place bets
|
57
|
+
# in both sides hence we need current probabilities for both outcomes.
|
58
|
+
if price_yes and price_no:
|
59
|
+
# If other outcome`s price is None, we set it to 0.
|
60
|
+
total_price = sum(
|
61
|
+
price if price is not None else 0.0 for price in price_data.values()
|
62
|
+
)
|
63
|
+
normalized_price_yes = price_yes / total_price
|
64
|
+
self._log_track_price_normalization_diff(
|
65
|
+
old_price=price_yes, normalized_price=normalized_price_yes
|
66
|
+
)
|
67
|
+
return Probability(normalized_price_yes)
|
68
|
+
else:
|
69
|
+
return None
|
70
|
+
|
71
|
+
def get_price_for_token(
|
72
|
+
self, token: ChecksumAddress, collateral_exchange_amount: Wei | None = None
|
73
|
+
) -> float | None:
|
74
|
+
collateral_exchange_amount = (
|
75
|
+
collateral_exchange_amount
|
76
|
+
if collateral_exchange_amount is not None
|
77
|
+
else CollateralToken(1).as_wei
|
78
|
+
)
|
79
|
+
|
80
|
+
try:
|
81
|
+
quote = get_quote(
|
82
|
+
amount_wei=collateral_exchange_amount,
|
83
|
+
sell_token=self.seer_market.collateral_token_contract_address_checksummed,
|
84
|
+
buy_token=token,
|
85
|
+
)
|
86
|
+
|
87
|
+
except Exception as e:
|
88
|
+
logger.warning(
|
89
|
+
f"Could not get quote for {token=} from Cow, exception {e=}. Falling back to pools. "
|
90
|
+
)
|
91
|
+
return self.get_token_price_from_pools(token=token)
|
92
|
+
|
93
|
+
collateral_exchange_amount = check_not_none(collateral_exchange_amount)
|
94
|
+
price = collateral_exchange_amount / float(quote.quote.buyAmount.root)
|
95
|
+
return Wei(str(price)).value
|
96
|
+
|
97
|
+
@staticmethod
|
98
|
+
def _pool_token0_matches_token(token: ChecksumAddress, pool: SeerPool) -> bool:
|
99
|
+
return pool.token0.id.hex().lower() == token.lower()
|
100
|
+
|
101
|
+
def get_token_price_from_pools(
|
102
|
+
self,
|
103
|
+
token: ChecksumAddress,
|
104
|
+
) -> float | None:
|
105
|
+
pool = SeerSubgraphHandler().get_pool_by_token(
|
106
|
+
token_address=token,
|
107
|
+
collateral_address=self.seer_market.collateral_token_contract_address_checksummed,
|
108
|
+
)
|
109
|
+
|
110
|
+
if not pool:
|
111
|
+
logger.warning(f"Could not find a pool for {token=}")
|
112
|
+
return None
|
113
|
+
|
114
|
+
# The mapping below is odd but surprisingly the Algebra subgraph delivers the token1Price
|
115
|
+
# for the token0 and the token0Price for the token1 pool.
|
116
|
+
# For example, in a outcomeYES (token0)/sDAI pool (token1), token1Price is the price of outcomeYES in units of sDAI.
|
117
|
+
price_in_collateral_units = (
|
118
|
+
pool.token1Price
|
119
|
+
if self._pool_token0_matches_token(token=token, pool=pool)
|
120
|
+
else pool.token0Price
|
121
|
+
)
|
122
|
+
return price_in_collateral_units
|
@@ -1,5 +1,7 @@
|
|
1
1
|
import typing as t
|
2
2
|
|
3
|
+
import tenacity
|
4
|
+
from eth_pydantic_types import HexStr
|
3
5
|
from eth_typing import ChecksumAddress
|
4
6
|
from web3 import Web3
|
5
7
|
from web3.types import TxReceipt
|
@@ -20,33 +22,32 @@ from prediction_market_agent_tooling.markets.agent_market import (
|
|
20
22
|
AgentMarket,
|
21
23
|
FilterBy,
|
22
24
|
ProcessedMarket,
|
23
|
-
ProcessedTradedMarket,
|
24
25
|
SortBy,
|
26
|
+
ProcessedTradedMarket,
|
25
27
|
)
|
26
|
-
from prediction_market_agent_tooling.markets.blockchain_utils import store_trades
|
27
28
|
from prediction_market_agent_tooling.markets.data_models import ExistingPosition
|
28
29
|
from prediction_market_agent_tooling.markets.market_fees import MarketFees
|
29
30
|
from prediction_market_agent_tooling.markets.omen.omen import OmenAgentMarket
|
30
31
|
from prediction_market_agent_tooling.markets.seer.data_models import (
|
31
|
-
NewMarketEvent,
|
32
32
|
SeerMarket,
|
33
33
|
SeerOutcomeEnum,
|
34
34
|
)
|
35
|
+
from prediction_market_agent_tooling.markets.seer.price_manager import PriceManager
|
35
36
|
from prediction_market_agent_tooling.markets.seer.seer_contracts import (
|
36
37
|
SeerMarketFactory,
|
37
38
|
)
|
38
39
|
from prediction_market_agent_tooling.markets.seer.seer_subgraph_handler import (
|
39
40
|
SeerSubgraphHandler,
|
40
41
|
)
|
42
|
+
from prediction_market_agent_tooling.markets.seer.subgraph_data_models import (
|
43
|
+
NewMarketEvent,
|
44
|
+
)
|
41
45
|
from prediction_market_agent_tooling.tools.contract import (
|
42
46
|
ContractERC20OnGnosisChain,
|
43
47
|
init_collateral_token_contract,
|
44
48
|
to_gnosis_chain_contract,
|
45
49
|
)
|
46
|
-
from prediction_market_agent_tooling.tools.cow.
|
47
|
-
CowManager,
|
48
|
-
NoLiquidityAvailableOnCowException,
|
49
|
-
)
|
50
|
+
from prediction_market_agent_tooling.tools.cow.cow_order import swap_tokens_waiting
|
50
51
|
from prediction_market_agent_tooling.tools.datetime_utc import DatetimeUTC
|
51
52
|
from prediction_market_agent_tooling.tools.tokens.auto_deposit import (
|
52
53
|
auto_deposit_collateral_token,
|
@@ -87,19 +88,16 @@ class SeerAgentMarket(AgentMarket):
|
|
87
88
|
agent_name: str,
|
88
89
|
) -> None:
|
89
90
|
"""On Seer, we have to store predictions along with trades, see `store_trades`."""
|
91
|
+
pass
|
90
92
|
|
91
93
|
def store_trades(
|
92
94
|
self,
|
93
95
|
traded_market: ProcessedTradedMarket | None,
|
94
96
|
keys: APIKeys,
|
95
97
|
agent_name: str,
|
98
|
+
web3: Web3 | None = None,
|
96
99
|
) -> None:
|
97
|
-
|
98
|
-
market_id=self.id,
|
99
|
-
traded_market=traded_market,
|
100
|
-
keys=keys,
|
101
|
-
agent_name=agent_name,
|
102
|
-
)
|
100
|
+
pass
|
103
101
|
|
104
102
|
def get_token_in_usd(self, x: CollateralToken) -> USD:
|
105
103
|
return get_token_in_usd(x, self.collateral_token_contract_address_checksummed)
|
@@ -109,20 +107,22 @@ class SeerAgentMarket(AgentMarket):
|
|
109
107
|
|
110
108
|
def get_buy_token_amount(
|
111
109
|
self, bet_amount: USD | CollateralToken, direction: bool
|
112
|
-
) -> OutcomeToken:
|
110
|
+
) -> OutcomeToken | None:
|
113
111
|
"""Returns number of outcome tokens returned for a given bet expressed in collateral units."""
|
114
112
|
|
115
113
|
outcome_token = self.get_wrapped_token_for_outcome(direction)
|
116
114
|
bet_amount_in_tokens = self.get_in_token(bet_amount)
|
117
|
-
bet_amount_in_wei = bet_amount_in_tokens.as_wei
|
118
115
|
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
collateral_token=self.collateral_token_contract_address_checksummed,
|
116
|
+
p = PriceManager.build(market_id=HexBytes(HexStr(self.id)))
|
117
|
+
price_in_collateral_units = p.get_price_for_token(
|
118
|
+
token=outcome_token, collateral_exchange_amount=bet_amount_in_tokens.as_wei
|
123
119
|
)
|
124
|
-
|
125
|
-
|
120
|
+
if not price_in_collateral_units:
|
121
|
+
logger.info(f"Could not get price for token {outcome_token}")
|
122
|
+
return None
|
123
|
+
|
124
|
+
amount_outcome_tokens = bet_amount_in_tokens.value / price_in_collateral_units
|
125
|
+
return OutcomeToken(amount_outcome_tokens)
|
126
126
|
|
127
127
|
def get_outcome_str_from_bool(self, outcome: bool) -> OutcomeStr:
|
128
128
|
outcome_translated = SeerOutcomeEnum.from_bool(outcome)
|
@@ -185,7 +185,17 @@ class SeerAgentMarket(AgentMarket):
|
|
185
185
|
return OmenAgentMarket.verify_operational_balance(api_keys=api_keys)
|
186
186
|
|
187
187
|
@staticmethod
|
188
|
-
def
|
188
|
+
def from_data_model_with_subgraph(
|
189
|
+
model: SeerMarket, seer_subgraph: SeerSubgraphHandler
|
190
|
+
) -> t.Optional["SeerAgentMarket"]:
|
191
|
+
p = PriceManager(seer_market=model, seer_subgraph=seer_subgraph)
|
192
|
+
current_p_yes = p.current_p_yes()
|
193
|
+
if not current_p_yes:
|
194
|
+
logger.info(
|
195
|
+
f"p_yes for market {model.id.hex()} could not be calculated. Skipping."
|
196
|
+
)
|
197
|
+
return None
|
198
|
+
|
189
199
|
return SeerAgentMarket(
|
190
200
|
id=model.id.hex(),
|
191
201
|
question=model.title,
|
@@ -201,7 +211,7 @@ class SeerAgentMarket(AgentMarket):
|
|
201
211
|
outcome_token_pool=None,
|
202
212
|
resolution=model.get_resolution_enum(),
|
203
213
|
volume=None,
|
204
|
-
current_p_yes=
|
214
|
+
current_p_yes=current_p_yes,
|
205
215
|
seer_outcomes=model.outcome_as_enums,
|
206
216
|
)
|
207
217
|
|
@@ -213,31 +223,31 @@ class SeerAgentMarket(AgentMarket):
|
|
213
223
|
created_after: t.Optional[DatetimeUTC] = None,
|
214
224
|
excluded_questions: set[str] | None = None,
|
215
225
|
) -> t.Sequence["SeerAgentMarket"]:
|
226
|
+
seer_subgraph = SeerSubgraphHandler()
|
227
|
+
markets = seer_subgraph.get_binary_markets(
|
228
|
+
limit=limit, sort_by=sort_by, filter_by=filter_by
|
229
|
+
)
|
230
|
+
|
231
|
+
# We exclude the None values below because `from_data_model_with_subgraph` can return None, which
|
232
|
+
# represents an invalid market.
|
216
233
|
return [
|
217
|
-
|
218
|
-
for m in
|
219
|
-
|
220
|
-
|
221
|
-
|
234
|
+
market
|
235
|
+
for m in markets
|
236
|
+
if (
|
237
|
+
market := SeerAgentMarket.from_data_model_with_subgraph(
|
238
|
+
model=m, seer_subgraph=seer_subgraph
|
239
|
+
)
|
222
240
|
)
|
241
|
+
is not None
|
223
242
|
]
|
224
243
|
|
225
244
|
def has_liquidity_for_outcome(self, outcome: bool) -> bool:
|
226
245
|
outcome_token = self.get_wrapped_token_for_outcome(outcome)
|
227
|
-
|
228
|
-
|
229
|
-
|
230
|
-
|
231
|
-
|
232
|
-
1
|
233
|
-
).as_wei, # we take 1 as a baseline value for common trades the agents take.
|
234
|
-
)
|
235
|
-
return True
|
236
|
-
except NoLiquidityAvailableOnCowException:
|
237
|
-
logger.info(
|
238
|
-
f"Could not get a quote for {outcome_token=} {outcome=}, returning no liquidity"
|
239
|
-
)
|
240
|
-
return False
|
246
|
+
pool = SeerSubgraphHandler().get_pool_by_token(
|
247
|
+
token_address=outcome_token,
|
248
|
+
collateral_address=self.collateral_token_contract_address_checksummed,
|
249
|
+
)
|
250
|
+
return pool is not None and pool.liquidity > 0
|
241
251
|
|
242
252
|
def has_liquidity(self) -> bool:
|
243
253
|
# We conservatively define a market as having liquidity if it has liquidity for the `True` outcome token AND the `False` outcome token.
|
@@ -281,14 +291,62 @@ class SeerAgentMarket(AgentMarket):
|
|
281
291
|
)
|
282
292
|
|
283
293
|
outcome_token = self.get_wrapped_token_for_outcome(outcome)
|
284
|
-
|
285
|
-
|
286
|
-
|
294
|
+
|
295
|
+
# Sell sDAI using token address
|
296
|
+
order_metadata = swap_tokens_waiting(
|
297
|
+
amount_wei=amount_wei,
|
287
298
|
sell_token=collateral_contract.address,
|
288
299
|
buy_token=outcome_token,
|
289
300
|
api_keys=api_keys,
|
290
301
|
web3=web3,
|
291
302
|
)
|
303
|
+
logger.debug(
|
304
|
+
f"Purchased {outcome_token} in exchange for {collateral_contract.address}. Order details {order_metadata}"
|
305
|
+
)
|
306
|
+
|
307
|
+
return order_metadata.uid.root
|
308
|
+
|
309
|
+
@tenacity.retry(
|
310
|
+
stop=tenacity.stop_after_attempt(3),
|
311
|
+
wait=tenacity.wait_fixed(1),
|
312
|
+
after=lambda x: logger.debug(
|
313
|
+
f"seer_sell_outcome_tx failed, {x.attempt_number=}."
|
314
|
+
),
|
315
|
+
)
|
316
|
+
def sell_tokens(
|
317
|
+
self,
|
318
|
+
outcome: bool,
|
319
|
+
amount: USD | OutcomeToken,
|
320
|
+
auto_withdraw: bool = True,
|
321
|
+
api_keys: APIKeys | None = None,
|
322
|
+
web3: Web3 | None = None,
|
323
|
+
) -> str:
|
324
|
+
"""
|
325
|
+
Sells the given number of shares for the given outcome in the given market.
|
326
|
+
"""
|
327
|
+
outcome_token = self.get_wrapped_token_for_outcome(outcome)
|
328
|
+
api_keys = api_keys if api_keys is not None else APIKeys()
|
329
|
+
|
330
|
+
########
|
331
|
+
token_amount = (
|
332
|
+
amount.as_outcome_wei.as_wei
|
333
|
+
if isinstance(amount, OutcomeToken)
|
334
|
+
else self.get_in_token(amount).as_wei
|
335
|
+
)
|
336
|
+
|
337
|
+
order_metadata = swap_tokens_waiting(
|
338
|
+
amount_wei=token_amount,
|
339
|
+
sell_token=outcome_token,
|
340
|
+
buy_token=Web3.to_checksum_address(
|
341
|
+
self.collateral_token_contract_address_checksummed
|
342
|
+
),
|
343
|
+
api_keys=api_keys,
|
344
|
+
web3=web3,
|
345
|
+
)
|
346
|
+
|
347
|
+
logger.debug(
|
348
|
+
f"Sold {outcome_token} in exchange for {self.collateral_token_contract_address_checksummed}. Order details {order_metadata}"
|
349
|
+
)
|
292
350
|
|
293
351
|
return order_metadata.uid.root
|
294
352
|
|
@@ -2,7 +2,6 @@ import os
|
|
2
2
|
import typing as t
|
3
3
|
|
4
4
|
from web3 import Web3
|
5
|
-
from web3.types import TxReceipt
|
6
5
|
|
7
6
|
from prediction_market_agent_tooling.config import APIKeys
|
8
7
|
from prediction_market_agent_tooling.gtypes import (
|
@@ -10,8 +9,9 @@ from prediction_market_agent_tooling.gtypes import (
|
|
10
9
|
ChecksumAddress,
|
11
10
|
OutcomeStr,
|
12
11
|
xDai,
|
12
|
+
TxReceipt,
|
13
13
|
)
|
14
|
-
from prediction_market_agent_tooling.markets.seer.
|
14
|
+
from prediction_market_agent_tooling.markets.seer.subgraph_data_models import (
|
15
15
|
CreateCategoricalMarketsParams,
|
16
16
|
)
|
17
17
|
from prediction_market_agent_tooling.tools.contract import (
|
@@ -47,9 +47,9 @@ class SeerMarketFactory(ContractOnGnosisChain):
|
|
47
47
|
token_names=[
|
48
48
|
o.upper() for o in outcomes
|
49
49
|
], # Following usual token names on Seer (YES,NO).
|
50
|
-
min_bond=min_bond.as_xdai_wei.
|
50
|
+
min_bond=min_bond.as_xdai_wei.as_wei,
|
51
51
|
opening_time=int(opening_time.timestamp()),
|
52
|
-
outcomes=outcomes,
|
52
|
+
outcomes=list(outcomes),
|
53
53
|
lang=language,
|
54
54
|
category=category,
|
55
55
|
)
|
@@ -5,14 +5,14 @@ from typing import Any
|
|
5
5
|
from subgrounds import FieldPath
|
6
6
|
from web3.constants import ADDRESS_ZERO
|
7
7
|
|
8
|
+
from prediction_market_agent_tooling.gtypes import ChecksumAddress
|
9
|
+
from prediction_market_agent_tooling.loggers import logger
|
8
10
|
from prediction_market_agent_tooling.markets.agent_market import FilterBy, SortBy
|
9
11
|
from prediction_market_agent_tooling.markets.base_subgraph_handler import (
|
10
12
|
BaseSubgraphHandler,
|
11
13
|
)
|
12
|
-
from prediction_market_agent_tooling.markets.seer.data_models import
|
13
|
-
|
14
|
-
SeerPool,
|
15
|
-
)
|
14
|
+
from prediction_market_agent_tooling.markets.seer.data_models import SeerMarket
|
15
|
+
from prediction_market_agent_tooling.markets.seer.subgraph_data_models import SeerPool
|
16
16
|
from prediction_market_agent_tooling.tools.hexbytes_custom import HexBytes
|
17
17
|
from prediction_market_agent_tooling.tools.utils import to_int_timestamp, utcnow
|
18
18
|
from prediction_market_agent_tooling.tools.web3_utils import unwrap_generic_value
|
@@ -50,6 +50,7 @@ class SeerSubgraphHandler(BaseSubgraphHandler):
|
|
50
50
|
markets_field.creator,
|
51
51
|
markets_field.conditionId,
|
52
52
|
markets_field.marketName,
|
53
|
+
markets_field.outcomesSupply,
|
53
54
|
markets_field.parentOutcome,
|
54
55
|
markets_field.outcomes,
|
55
56
|
markets_field.payoutReported,
|
@@ -121,12 +122,16 @@ class SeerSubgraphHandler(BaseSubgraphHandler):
|
|
121
122
|
match sort_by:
|
122
123
|
case SortBy.NEWEST:
|
123
124
|
sort_direction = "desc"
|
124
|
-
sort_by_field = self.seer_subgraph.Market.
|
125
|
+
sort_by_field = self.seer_subgraph.Market.blockTimestamp
|
125
126
|
case SortBy.CLOSING_SOONEST:
|
126
127
|
sort_direction = "asc"
|
127
|
-
sort_by_field = self.seer_subgraph.Market.
|
128
|
-
|
129
|
-
|
128
|
+
sort_by_field = self.seer_subgraph.Market.openingTs
|
129
|
+
case SortBy.HIGHEST_LIQUIDITY | SortBy.LOWEST_LIQUIDITY:
|
130
|
+
sort_direction = (
|
131
|
+
"desc" if sort_by == SortBy.HIGHEST_LIQUIDITY else "asc"
|
132
|
+
)
|
133
|
+
sort_by_field = self.seer_subgraph.Market.outcomesSupply
|
134
|
+
case SortBy.NONE:
|
130
135
|
sort_direction = None
|
131
136
|
sort_by_field = None
|
132
137
|
case _:
|
@@ -201,6 +206,9 @@ class SeerSubgraphHandler(BaseSubgraphHandler):
|
|
201
206
|
fields = [
|
202
207
|
pools_field.id,
|
203
208
|
pools_field.liquidity,
|
209
|
+
pools_field.sqrtPrice,
|
210
|
+
pools_field.token0Price,
|
211
|
+
pools_field.token1Price,
|
204
212
|
pools_field.token0.id,
|
205
213
|
pools_field.token0.name,
|
206
214
|
pools_field.token0.symbol,
|
@@ -210,19 +218,40 @@ class SeerSubgraphHandler(BaseSubgraphHandler):
|
|
210
218
|
]
|
211
219
|
return fields
|
212
220
|
|
213
|
-
def
|
221
|
+
def get_pool_by_token(
|
222
|
+
self, token_address: ChecksumAddress, collateral_address: ChecksumAddress
|
223
|
+
) -> SeerPool | None:
|
214
224
|
# We iterate through the wrapped tokens and put them in a where clause so that we hit the subgraph endpoint just once.
|
215
225
|
wheres = []
|
216
|
-
|
217
|
-
|
218
|
-
|
219
|
-
|
220
|
-
|
221
|
-
|
222
|
-
|
226
|
+
wheres.extend(
|
227
|
+
[
|
228
|
+
{
|
229
|
+
"token0": token_address.lower(),
|
230
|
+
"token1": collateral_address.lower(),
|
231
|
+
},
|
232
|
+
{
|
233
|
+
"token0": collateral_address.lower(),
|
234
|
+
"token1": token_address.lower(),
|
235
|
+
},
|
236
|
+
]
|
237
|
+
)
|
238
|
+
|
239
|
+
optional_params = {}
|
240
|
+
optional_params["orderBy"] = self.swapr_algebra_subgraph.Pool.liquidity
|
241
|
+
optional_params["orderDirection"] = "desc"
|
242
|
+
|
223
243
|
pools_field = self.swapr_algebra_subgraph.Query.pools(
|
224
|
-
where=unwrap_generic_value({"or": wheres}
|
244
|
+
where=unwrap_generic_value({"or": wheres}, **optional_params
|
245
|
+
)
|
225
246
|
)
|
226
247
|
fields = self._get_fields_for_pools(pools_field)
|
227
248
|
pools = self.do_query(fields=fields, pydantic_model=SeerPool)
|
228
|
-
|
249
|
+
# We assume there is only one pool for outcomeToken/sDAI.
|
250
|
+
if len(pools) > 1:
|
251
|
+
logger.info(
|
252
|
+
f"Multiple pools found for token {token_address}, selecting the first."
|
253
|
+
)
|
254
|
+
if pools:
|
255
|
+
# We select the first one
|
256
|
+
return pools[0]
|
257
|
+
return None
|
@@ -0,0 +1,57 @@
|
|
1
|
+
from pydantic import BaseModel, ConfigDict, Field
|
2
|
+
from web3.constants import ADDRESS_ZERO
|
3
|
+
|
4
|
+
from prediction_market_agent_tooling.gtypes import HexAddress, HexBytes, Wei, OutcomeStr
|
5
|
+
|
6
|
+
|
7
|
+
class SeerToken(BaseModel):
|
8
|
+
id: HexBytes
|
9
|
+
name: str
|
10
|
+
symbol: str
|
11
|
+
|
12
|
+
|
13
|
+
class SeerPool(BaseModel):
|
14
|
+
model_config = ConfigDict(populate_by_name=True)
|
15
|
+
id: HexBytes
|
16
|
+
liquidity: int
|
17
|
+
token0: SeerToken
|
18
|
+
token1: SeerToken
|
19
|
+
token0Price: float
|
20
|
+
token1Price: float
|
21
|
+
sqrtPrice: int
|
22
|
+
|
23
|
+
|
24
|
+
class NewMarketEvent(BaseModel):
|
25
|
+
market: HexAddress
|
26
|
+
marketName: str
|
27
|
+
parentMarket: HexAddress
|
28
|
+
conditionId: HexBytes
|
29
|
+
questionId: HexBytes
|
30
|
+
questionsIds: list[HexBytes]
|
31
|
+
|
32
|
+
|
33
|
+
class CreateCategoricalMarketsParams(BaseModel):
|
34
|
+
model_config = ConfigDict(populate_by_name=True)
|
35
|
+
|
36
|
+
market_name: str = Field(..., alias="marketName")
|
37
|
+
outcomes: list[OutcomeStr]
|
38
|
+
# Only relevant for scalar markets
|
39
|
+
question_start: str = Field(alias="questionStart", default="")
|
40
|
+
question_end: str = Field(alias="questionEnd", default="")
|
41
|
+
outcome_type: str = Field(alias="outcomeType", default="")
|
42
|
+
|
43
|
+
# Not needed for non-conditional markets.
|
44
|
+
parent_outcome: int = Field(alias="parentOutcome", default=0)
|
45
|
+
parent_market: HexAddress = Field(alias="parentMarket", default=ADDRESS_ZERO)
|
46
|
+
|
47
|
+
category: str
|
48
|
+
lang: str
|
49
|
+
lower_bound: int = Field(alias="lowerBound", default=0)
|
50
|
+
upper_bound: int = Field(alias="upperBound", default=0)
|
51
|
+
min_bond: Wei = Field(..., alias="minBond")
|
52
|
+
opening_time: int = Field(..., alias="openingTime")
|
53
|
+
token_names: list[str] = Field(..., alias="tokenNames")
|
54
|
+
|
55
|
+
|
56
|
+
class SeerParentMarket(BaseModel):
|
57
|
+
id: HexBytes
|
@@ -207,6 +207,17 @@ class ContractERC20BaseClass(ContractBaseClass):
|
|
207
207
|
value: str = self._cache[cache_key]
|
208
208
|
return value
|
209
209
|
|
210
|
+
def allowance(
|
211
|
+
self,
|
212
|
+
owner: ChecksumAddress,
|
213
|
+
for_address: ChecksumAddress,
|
214
|
+
web3: Web3 | None = None,
|
215
|
+
) -> int:
|
216
|
+
allowance_for_user: int = self.call(
|
217
|
+
"allowance", function_params=[owner, for_address], web3=web3
|
218
|
+
)
|
219
|
+
return allowance_for_user
|
220
|
+
|
210
221
|
def approve(
|
211
222
|
self,
|
212
223
|
api_keys: APIKeys,
|
@@ -4,9 +4,11 @@ from datetime import timedelta
|
|
4
4
|
import httpx
|
5
5
|
import tenacity
|
6
6
|
from cowdao_cowpy import swap_tokens
|
7
|
+
from cowdao_cowpy.common.api.errors import UnexpectedResponseError
|
7
8
|
from cowdao_cowpy.common.chains import Chain
|
8
9
|
from cowdao_cowpy.common.config import SupportedChainId
|
9
10
|
from cowdao_cowpy.common.constants import CowContractAddress
|
11
|
+
from cowdao_cowpy.cow.swap import get_order_quote
|
10
12
|
from cowdao_cowpy.order_book.api import OrderBookApi
|
11
13
|
from cowdao_cowpy.order_book.config import Envs, OrderBookAPIConfigFactory
|
12
14
|
from cowdao_cowpy.order_book.generated.model import (
|
@@ -14,14 +16,15 @@ from cowdao_cowpy.order_book.generated.model import (
|
|
14
16
|
OrderMetaData,
|
15
17
|
OrderQuoteRequest,
|
16
18
|
OrderQuoteSide1,
|
17
|
-
OrderQuoteSide3,
|
18
|
-
OrderQuoteSideKindBuy,
|
19
19
|
OrderQuoteSideKindSell,
|
20
20
|
OrderStatus,
|
21
21
|
TokenAmount,
|
22
|
+
OrderQuoteResponse,
|
23
|
+
OrderQuoteSide3,
|
24
|
+
OrderQuoteSideKindBuy,
|
22
25
|
)
|
23
26
|
from eth_account.signers.local import LocalAccount
|
24
|
-
from
|
27
|
+
from tenacity import stop_after_attempt, wait_fixed, retry_if_not_exception_type
|
25
28
|
from web3 import Web3
|
26
29
|
|
27
30
|
from prediction_market_agent_tooling.config import APIKeys
|
@@ -35,6 +38,10 @@ class OrderStatusError(Exception):
|
|
35
38
|
pass
|
36
39
|
|
37
40
|
|
41
|
+
class NoLiquidityAvailableOnCowException(Exception):
|
42
|
+
"""Custom exception for handling case where no liquidity available."""
|
43
|
+
|
44
|
+
|
38
45
|
def get_order_book_api(env: Envs, chain: Chain) -> OrderBookApi:
|
39
46
|
chain_id = SupportedChainId(chain.value[0])
|
40
47
|
return OrderBookApi(OrderBookAPIConfigFactory.get_config(env, chain_id))
|
@@ -74,17 +81,17 @@ def get_sell_token_amount(
|
|
74
81
|
|
75
82
|
|
76
83
|
@tenacity.retry(
|
77
|
-
stop=
|
78
|
-
wait=
|
79
|
-
|
84
|
+
stop=stop_after_attempt(3),
|
85
|
+
wait=wait_fixed(1),
|
86
|
+
retry=retry_if_not_exception_type(NoLiquidityAvailableOnCowException),
|
80
87
|
)
|
81
|
-
def
|
82
|
-
|
88
|
+
def get_quote(
|
89
|
+
amount_wei: Wei,
|
83
90
|
sell_token: ChecksumAddress,
|
84
91
|
buy_token: ChecksumAddress,
|
85
92
|
chain: Chain = Chain.GNOSIS,
|
86
93
|
env: Envs = "prod",
|
87
|
-
) ->
|
94
|
+
) -> OrderQuoteResponse:
|
88
95
|
order_book_api = get_order_book_api(env, chain)
|
89
96
|
order_quote_request = OrderQuoteRequest(
|
90
97
|
sellToken=Address(sell_token),
|
@@ -95,20 +102,47 @@ def get_buy_token_amount(
|
|
95
102
|
)
|
96
103
|
order_side = OrderQuoteSide1(
|
97
104
|
kind=OrderQuoteSideKindSell.sell,
|
98
|
-
sellAmountBeforeFee=TokenAmount(str(
|
105
|
+
sellAmountBeforeFee=TokenAmount(str(amount_wei)),
|
99
106
|
)
|
100
|
-
|
101
|
-
|
107
|
+
|
108
|
+
try:
|
109
|
+
order_quote = asyncio.run(
|
110
|
+
get_order_quote(
|
111
|
+
order_quote_request=order_quote_request,
|
112
|
+
order_side=order_side,
|
113
|
+
order_book_api=order_book_api,
|
114
|
+
)
|
115
|
+
)
|
116
|
+
|
117
|
+
return order_quote
|
118
|
+
|
119
|
+
except UnexpectedResponseError as e1:
|
120
|
+
if "NoLiquidity" in e1.message:
|
121
|
+
raise NoLiquidityAvailableOnCowException(e1.message)
|
122
|
+
logger.warning(f"Found unexpected Cow response error: {e1}")
|
123
|
+
raise
|
124
|
+
except Exception as e:
|
125
|
+
logger.warning(f"Found unhandled Cow response error: {e}")
|
126
|
+
raise
|
127
|
+
|
128
|
+
|
129
|
+
def get_buy_token_amount_else_raise(
|
130
|
+
amount_wei: Wei,
|
131
|
+
sell_token: ChecksumAddress,
|
132
|
+
buy_token: ChecksumAddress,
|
133
|
+
chain: Chain = Chain.GNOSIS,
|
134
|
+
env: Envs = "prod",
|
135
|
+
) -> Wei:
|
136
|
+
order_quote = get_quote(
|
137
|
+
amount_wei=amount_wei,
|
138
|
+
sell_token=sell_token,
|
139
|
+
buy_token=buy_token,
|
140
|
+
chain=chain,
|
141
|
+
env=env,
|
102
142
|
)
|
103
143
|
return Wei(order_quote.quote.buyAmount.root)
|
104
144
|
|
105
145
|
|
106
|
-
@tenacity.retry(
|
107
|
-
stop=tenacity.stop_after_attempt(3),
|
108
|
-
wait=tenacity.wait_fixed(1),
|
109
|
-
retry=tenacity.retry_if_not_exception_type((TimeoutError, OrderStatusError)),
|
110
|
-
after=lambda x: logger.debug(f"swap_tokens_waiting failed, {x.attempt_number=}."),
|
111
|
-
)
|
112
146
|
def swap_tokens_waiting(
|
113
147
|
amount_wei: Wei,
|
114
148
|
sell_token: ChecksumAddress,
|
@@ -169,7 +203,7 @@ async def swap_tokens_waiting_async(
|
|
169
203
|
OrderStatus.cancelled,
|
170
204
|
OrderStatus.expired,
|
171
205
|
):
|
172
|
-
raise
|
206
|
+
raise ValueError(f"Order {order.uid} failed. {order.url}")
|
173
207
|
|
174
208
|
if utcnow() - start_time > timeout:
|
175
209
|
raise TimeoutError(
|
@@ -1,4 +1,3 @@
|
|
1
|
-
from eth_typing.evm import ChecksumAddress
|
2
1
|
from web3 import Web3
|
3
2
|
|
4
3
|
from prediction_market_agent_tooling.gtypes import ChecksumAddress, Wei
|
@@ -7,7 +6,9 @@ from prediction_market_agent_tooling.tools.contract import (
|
|
7
6
|
init_collateral_token_contract,
|
8
7
|
to_gnosis_chain_contract,
|
9
8
|
)
|
10
|
-
from prediction_market_agent_tooling.tools.cow.cow_order import
|
9
|
+
from prediction_market_agent_tooling.tools.cow.cow_order import (
|
10
|
+
get_buy_token_amount_else_raise,
|
11
|
+
)
|
11
12
|
|
12
13
|
|
13
14
|
def convert_to_another_token(
|
@@ -39,7 +40,7 @@ def convert_to_another_token(
|
|
39
40
|
return from_token_contract.convertToAssets(amount)
|
40
41
|
|
41
42
|
else:
|
42
|
-
return
|
43
|
+
return get_buy_token_amount_else_raise(
|
43
44
|
amount,
|
44
45
|
from_token,
|
45
46
|
to_token,
|
@@ -1,5 +1,4 @@
|
|
1
1
|
from cachetools import TTLCache, cached
|
2
|
-
from eth_typing.evm import ChecksumAddress
|
3
2
|
|
4
3
|
from prediction_market_agent_tooling.gtypes import (
|
5
4
|
USD,
|
@@ -12,7 +11,9 @@ from prediction_market_agent_tooling.markets.omen.omen_constants import (
|
|
12
11
|
WRAPPED_XDAI_CONTRACT_ADDRESS,
|
13
12
|
)
|
14
13
|
from prediction_market_agent_tooling.tools.contract import ContractERC4626OnGnosisChain
|
15
|
-
from prediction_market_agent_tooling.tools.cow.cow_order import
|
14
|
+
from prediction_market_agent_tooling.tools.cow.cow_order import (
|
15
|
+
get_buy_token_amount_else_raise,
|
16
|
+
)
|
16
17
|
|
17
18
|
|
18
19
|
def get_usd_in_xdai(amount: USD) -> xDai:
|
@@ -48,8 +49,8 @@ def get_single_token_to_usd_rate(token_address: ChecksumAddress) -> USD:
|
|
48
49
|
.convertToAssets(CollateralToken(1).as_wei)
|
49
50
|
.as_token.value
|
50
51
|
)
|
51
|
-
in_wei =
|
52
|
-
|
52
|
+
in_wei = get_buy_token_amount_else_raise(
|
53
|
+
amount_wei=CollateralToken(1).as_wei,
|
53
54
|
sell_token=token_address,
|
54
55
|
buy_token=WRAPPED_XDAI_CONTRACT_ADDRESS,
|
55
56
|
)
|
@@ -70,8 +71,8 @@ def get_single_usd_to_token_rate(token_address: ChecksumAddress) -> CollateralTo
|
|
70
71
|
.convertToShares(CollateralToken(1).as_wei)
|
71
72
|
.as_token.value
|
72
73
|
)
|
73
|
-
in_wei =
|
74
|
-
|
74
|
+
in_wei = get_buy_token_amount_else_raise(
|
75
|
+
amount_wei=CollateralToken(1).as_wei,
|
75
76
|
sell_token=WRAPPED_XDAI_CONTRACT_ADDRESS,
|
76
77
|
buy_token=token_address,
|
77
78
|
)
|
@@ -23,7 +23,7 @@ prediction_market_agent_tooling/benchmark/utils.py,sha256=D0MfUkVZllmvcU0VOurk9t
|
|
23
23
|
prediction_market_agent_tooling/config.py,sha256=So5l8KbgmzcCpxzzf13TNrEJPu_4iQnUDhzus6XRvSc,10151
|
24
24
|
prediction_market_agent_tooling/deploy/agent.py,sha256=OyrhPOjByQOAi1_VWhef7ieKqREjVhvjGHgnUIQc3gI,25877
|
25
25
|
prediction_market_agent_tooling/deploy/agent_example.py,sha256=dIIdZashExWk9tOdyDjw87AuUcGyM7jYxNChYrVK2dM,1001
|
26
|
-
prediction_market_agent_tooling/deploy/betting_strategy.py,sha256=
|
26
|
+
prediction_market_agent_tooling/deploy/betting_strategy.py,sha256=p25t7VU7I4hSkSl6SpzI_W55kLbYEySQdBqeschmARY,12918
|
27
27
|
prediction_market_agent_tooling/deploy/constants.py,sha256=M5ty8URipYMGe_G-RzxRydK3AFL6CyvmqCraJUrLBnE,82
|
28
28
|
prediction_market_agent_tooling/deploy/gcp/deploy.py,sha256=CYUgnfy-9XVk04kkxA_5yp0GE9Mw5caYqlFUZQ2j3ks,3739
|
29
29
|
prediction_market_agent_tooling/deploy/gcp/kubernetes_models.py,sha256=OsPboCFGiZKsvGyntGZHwdqPlLTthITkNF5rJFvGgU8,2582
|
@@ -34,9 +34,9 @@ prediction_market_agent_tooling/jobs/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeu
|
|
34
34
|
prediction_market_agent_tooling/jobs/jobs_models.py,sha256=8vYafsK1cqMWQtjBoq9rruroF84xAVD00vBTMWH6QMg,2166
|
35
35
|
prediction_market_agent_tooling/jobs/omen/omen_jobs.py,sha256=Pf6QxPXGyie-2l_wZUjaGPTjZTlpv50_JhP40mULBaU,5048
|
36
36
|
prediction_market_agent_tooling/loggers.py,sha256=MvCkQSJL2_0yErNatqr81sJlc4aOgPzDp9VNrIhKUcc,4140
|
37
|
-
prediction_market_agent_tooling/markets/agent_market.py,sha256=
|
37
|
+
prediction_market_agent_tooling/markets/agent_market.py,sha256=1NomilM0GCXcRq_1N_cr2AbSK5ONTowFeRbrhc7V5zE,14929
|
38
38
|
prediction_market_agent_tooling/markets/base_subgraph_handler.py,sha256=7RaYO_4qAmQ6ZGM8oPK2-CkiJfKmV9MxM-rJlduaecU,1971
|
39
|
-
prediction_market_agent_tooling/markets/blockchain_utils.py,sha256=
|
39
|
+
prediction_market_agent_tooling/markets/blockchain_utils.py,sha256=rm-nh-FuIMuH3AuneRNfkwOfHc7Cs7DnPAcHtuTFcuw,2363
|
40
40
|
prediction_market_agent_tooling/markets/categorize.py,sha256=jsoHWvZk9pU6n17oWSCcCxNNYVwlb_NXsZxKRI7vmsk,1301
|
41
41
|
prediction_market_agent_tooling/markets/data_models.py,sha256=_R9Hr5zwGLpZLPXq0Jo2wZRNRQyOnSi3WVQJZ81syuk,4541
|
42
42
|
prediction_market_agent_tooling/markets/manifold/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
@@ -51,7 +51,7 @@ prediction_market_agent_tooling/markets/metaculus/data_models.py,sha256=FaBCTPPe
|
|
51
51
|
prediction_market_agent_tooling/markets/metaculus/metaculus.py,sha256=86TIx6cavEWc8Cv4KpZxSvwiSw9oFybXE3YB49pg-CA,4377
|
52
52
|
prediction_market_agent_tooling/markets/omen/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
53
53
|
prediction_market_agent_tooling/markets/omen/data_models.py,sha256=URvplDcTLeL6vfpkSgi8ln2iEKxt8w5qf6mTZkSyrCo,29189
|
54
|
-
prediction_market_agent_tooling/markets/omen/omen.py,sha256=
|
54
|
+
prediction_market_agent_tooling/markets/omen/omen.py,sha256=Jf2qSJJn0UUISpi24xYRwoVycGuYE42kZ2z1HRGl43w,51927
|
55
55
|
prediction_market_agent_tooling/markets/omen/omen_constants.py,sha256=D9oflYKafLQiHYtB5sScMHqmXyzM8JP8J0yATmc4SQQ,233
|
56
56
|
prediction_market_agent_tooling/markets/omen/omen_contracts.py,sha256=bCC9A7ZTJxMDJcPbl3jof6HcAGGHv1BrFq3RRWbkQ_c,28739
|
57
57
|
prediction_market_agent_tooling/markets/omen/omen_resolving.py,sha256=Cyi9Ci-Z-K8WCZWVLs9oSuJC6qRobi_CpqDCno_5F-0,10238
|
@@ -61,10 +61,12 @@ prediction_market_agent_tooling/markets/polymarket/data_models.py,sha256=utGN-Lh
|
|
61
61
|
prediction_market_agent_tooling/markets/polymarket/data_models_web.py,sha256=LVEsNw2nUx5poiU1m803NNqG5-fs8-MODQRyGLqy4mE,12585
|
62
62
|
prediction_market_agent_tooling/markets/polymarket/polymarket.py,sha256=6rc9qulPl90MxXKB55XiiWKLhjfAyG_eUzAlqpq1UIE,3339
|
63
63
|
prediction_market_agent_tooling/markets/polymarket/utils.py,sha256=8kTeVjXPcXC6DkDvWYsZQLY7x8DS6CEp_yznSEazsNU,2037
|
64
|
-
prediction_market_agent_tooling/markets/seer/data_models.py,sha256=
|
65
|
-
prediction_market_agent_tooling/markets/seer/
|
66
|
-
prediction_market_agent_tooling/markets/seer/
|
67
|
-
prediction_market_agent_tooling/markets/seer/
|
64
|
+
prediction_market_agent_tooling/markets/seer/data_models.py,sha256=FwTOq9X2iJ7r3ijtE0evl8pMSbFPm4lUwuc9m7YsMVA,6373
|
65
|
+
prediction_market_agent_tooling/markets/seer/price_manager.py,sha256=_rQDowNVA3DAzOM2L3d3lb9jf6sBL1d1Z8cP-t5bpjw,4976
|
66
|
+
prediction_market_agent_tooling/markets/seer/seer.py,sha256=WFpXIFUdWDj30ey9XFXSkcj53E7JmfEuxfFyrKTLCU0,15249
|
67
|
+
prediction_market_agent_tooling/markets/seer/seer_contracts.py,sha256=NCZbeiZgPIwEZS2qM6F1_i72FTOHg7Zq1ZvzuU6atH8,2715
|
68
|
+
prediction_market_agent_tooling/markets/seer/seer_subgraph_handler.py,sha256=bAuMRBmPNax20RvB448U7_OfBtfRf6P6TouoYDu_OfM,9867
|
69
|
+
prediction_market_agent_tooling/markets/seer/subgraph_data_models.py,sha256=_i4x9o2ZvC2K2fgaYvjxSiRtzwx8_N-lWyAUCdA4pGU,1663
|
68
70
|
prediction_market_agent_tooling/monitor/financial_metrics/financial_metrics.py,sha256=fjIgjDIx5MhH5mwf7S0cspLOOSU3elYLhGYoIiM26mU,2746
|
69
71
|
prediction_market_agent_tooling/monitor/markets/manifold.py,sha256=TS4ERwTfQnot8dhekNyVNhJYf5ysYsjF-9v5_kM3aVI,3334
|
70
72
|
prediction_market_agent_tooling/monitor/markets/metaculus.py,sha256=LOnyWWBFdg10-cTWdb76nOsNjDloO8OfMT85GBzRCFI,1455
|
@@ -84,10 +86,9 @@ prediction_market_agent_tooling/tools/betting_strategies/utils.py,sha256=68zFWUj
|
|
84
86
|
prediction_market_agent_tooling/tools/caches/db_cache.py,sha256=dB8LNs2JvVRaFCeAKRmIQRwiirsMgtL31he8051wM-g,11431
|
85
87
|
prediction_market_agent_tooling/tools/caches/inmemory_cache.py,sha256=ZW5iI5rmjqeAebu5T7ftRnlkxiL02IC-MxCfDB80x7w,1506
|
86
88
|
prediction_market_agent_tooling/tools/caches/serializers.py,sha256=vFDx4fsPxclXp2q0sv27j4al_M_Tj9aR2JJP-xNHQXA,2151
|
87
|
-
prediction_market_agent_tooling/tools/contract.py,sha256=
|
89
|
+
prediction_market_agent_tooling/tools/contract.py,sha256=1ZFp_VoqTjM8cqOfAhco2Ht0DTqakjhZpuZUrAXr28Q,21332
|
88
90
|
prediction_market_agent_tooling/tools/costs.py,sha256=EaAJ7v9laD4VEV3d8B44M4u3_oEO_H16jRVCdoZ93Uw,954
|
89
|
-
prediction_market_agent_tooling/tools/cow/
|
90
|
-
prediction_market_agent_tooling/tools/cow/cow_order.py,sha256=4BV9t_RZU07SxwAzjTVkXKoQtm9l-roFHJR_cE8O04A,5837
|
91
|
+
prediction_market_agent_tooling/tools/cow/cow_order.py,sha256=6qwT6gF1ZsBuargTVKNNjPgmMqm__b1u4kXBk8pzwe0,6709
|
91
92
|
prediction_market_agent_tooling/tools/custom_exceptions.py,sha256=Fh8z1fbwONvP4-j7AmV_PuEcoqb6-QXa9PJ9m7guMcM,93
|
92
93
|
prediction_market_agent_tooling/tools/datetime_utc.py,sha256=8_WackjtjC8zHXrhQFTGQ6e6Fz_6llWoKR4CSFvIv9I,2766
|
93
94
|
prediction_market_agent_tooling/tools/db/db_manager.py,sha256=GtzHH1NLl8HwqC8Z7s6eTlIQXuV0blxfaV2PeQrBnfQ,3013
|
@@ -112,16 +113,16 @@ prediction_market_agent_tooling/tools/singleton.py,sha256=CiIELUiI-OeS7U7eeHEt0r
|
|
112
113
|
prediction_market_agent_tooling/tools/streamlit_user_login.py,sha256=NXEqfjT9Lc9QtliwSGRASIz1opjQ7Btme43H4qJbzgE,3010
|
113
114
|
prediction_market_agent_tooling/tools/tavily/tavily_models.py,sha256=5ldQs1pZe6uJ5eDAuP4OLpzmcqYShlIV67kttNFvGS0,342
|
114
115
|
prediction_market_agent_tooling/tools/tavily/tavily_search.py,sha256=pPs0qZNfJ7G-1ajfz0iaWOBQyiC0TbcShfrW8T39jtg,3859
|
115
|
-
prediction_market_agent_tooling/tools/tokens/auto_deposit.py,sha256=
|
116
|
+
prediction_market_agent_tooling/tools/tokens/auto_deposit.py,sha256=dO-2XUcsCVvuRpvSN4dr1ZEoVS3Ee9soisLQI3bX8CU,6737
|
116
117
|
prediction_market_agent_tooling/tools/tokens/auto_withdraw.py,sha256=22g0SIVmLlgITpdt3kPhJOw0sU4OPeBuYk_7xCrQr9U,2491
|
117
118
|
prediction_market_agent_tooling/tools/tokens/main_token.py,sha256=1rbwpdCusPgQIVFuo3m00nBZ_b2lCAoFVm67i-YDcEw,812
|
118
|
-
prediction_market_agent_tooling/tools/tokens/token_utils.py,sha256=
|
119
|
-
prediction_market_agent_tooling/tools/tokens/usd.py,sha256=
|
119
|
+
prediction_market_agent_tooling/tools/tokens/token_utils.py,sha256=fhs-FH9m9IbzGa-30R3ZleSKLeKfLEDoJ7F5Om285Vk,1369
|
120
|
+
prediction_market_agent_tooling/tools/tokens/usd.py,sha256=Qq8ofVCCMX-eo8mDlHv4gL9DV_bqHFpd63Wv5yE7cjY,3113
|
120
121
|
prediction_market_agent_tooling/tools/transaction_cache.py,sha256=K5YKNL2_tR10Iw2TD9fuP-CTGpBbZtNdgbd0B_R7pjg,1814
|
121
122
|
prediction_market_agent_tooling/tools/utils.py,sha256=1xsyBBJfiEdSoMlceB2F8o2sCb6Z8-qNz11pEJFrdyE,6566
|
122
123
|
prediction_market_agent_tooling/tools/web3_utils.py,sha256=eYCc1iWAVtqDKUPTwnMUHuYolPdwh_OTiM3-AdRgDp4,12198
|
123
|
-
prediction_market_agent_tooling-0.
|
124
|
-
prediction_market_agent_tooling-0.
|
125
|
-
prediction_market_agent_tooling-0.
|
126
|
-
prediction_market_agent_tooling-0.
|
127
|
-
prediction_market_agent_tooling-0.
|
124
|
+
prediction_market_agent_tooling-0.63.0.dev499.dist-info/LICENSE,sha256=6or154nLLU6bELzjh0mCreFjt0m2v72zLi3yHE0QbeE,7650
|
125
|
+
prediction_market_agent_tooling-0.63.0.dev499.dist-info/METADATA,sha256=c7gvHoM7e67d4LhV---3JRwzYiuGkSBgpOX3vXcZvYg,8696
|
126
|
+
prediction_market_agent_tooling-0.63.0.dev499.dist-info/WHEEL,sha256=XbeZDeTWKc1w7CSIyre5aMDU_-PohRwTQceYnisIYYY,88
|
127
|
+
prediction_market_agent_tooling-0.63.0.dev499.dist-info/entry_points.txt,sha256=m8PukHbeH5g0IAAmOf_1Ahm-sGAMdhSSRQmwtpmi2s8,81
|
128
|
+
prediction_market_agent_tooling-0.63.0.dev499.dist-info/RECORD,,
|
@@ -1,112 +0,0 @@
|
|
1
|
-
import asyncio
|
2
|
-
|
3
|
-
from cowdao_cowpy.common.api.errors import UnexpectedResponseError
|
4
|
-
from cowdao_cowpy.common.config import SupportedChainId
|
5
|
-
from cowdao_cowpy.cow.swap import get_order_quote
|
6
|
-
from cowdao_cowpy.order_book.api import OrderBookApi
|
7
|
-
from cowdao_cowpy.order_book.config import Envs, OrderBookAPIConfigFactory
|
8
|
-
from cowdao_cowpy.order_book.generated.model import (
|
9
|
-
Address,
|
10
|
-
OrderMetaData,
|
11
|
-
OrderQuoteRequest,
|
12
|
-
OrderQuoteResponse,
|
13
|
-
OrderQuoteSide1,
|
14
|
-
OrderQuoteSideKindSell,
|
15
|
-
)
|
16
|
-
from cowdao_cowpy.order_book.generated.model import TokenAmount as TokenAmountCow
|
17
|
-
from tenacity import retry, retry_if_not_exception_type, stop_after_attempt, wait_fixed
|
18
|
-
from web3 import Web3
|
19
|
-
from web3.constants import ADDRESS_ZERO
|
20
|
-
|
21
|
-
from prediction_market_agent_tooling.config import APIKeys
|
22
|
-
from prediction_market_agent_tooling.gtypes import ChecksumAddress, CollateralToken, Wei
|
23
|
-
from prediction_market_agent_tooling.loggers import logger
|
24
|
-
from prediction_market_agent_tooling.tools.cow.cow_order import swap_tokens_waiting
|
25
|
-
|
26
|
-
COW_ENV: Envs = "prod"
|
27
|
-
|
28
|
-
|
29
|
-
class NoLiquidityAvailableOnCowException(Exception):
|
30
|
-
"""Custom exception for handling case where no liquidity available."""
|
31
|
-
|
32
|
-
|
33
|
-
class CowManager:
|
34
|
-
def __init__(self) -> None:
|
35
|
-
self.order_book_api = OrderBookApi(
|
36
|
-
OrderBookAPIConfigFactory.get_config(COW_ENV, SupportedChainId.GNOSIS_CHAIN)
|
37
|
-
)
|
38
|
-
self.precision = 18 # number of token decimals from ERC1155 wrapped tokens.
|
39
|
-
|
40
|
-
@retry(
|
41
|
-
stop=stop_after_attempt(2),
|
42
|
-
wait=wait_fixed(2),
|
43
|
-
retry=retry_if_not_exception_type(NoLiquidityAvailableOnCowException),
|
44
|
-
)
|
45
|
-
def get_quote(
|
46
|
-
self,
|
47
|
-
collateral_token: ChecksumAddress,
|
48
|
-
buy_token: ChecksumAddress,
|
49
|
-
sell_amount: Wei,
|
50
|
-
) -> OrderQuoteResponse:
|
51
|
-
"""
|
52
|
-
Quote the price for a sell order.
|
53
|
-
|
54
|
-
Parameters:
|
55
|
-
- collateral_token: The token being sold.
|
56
|
-
- buy_token: The token being bought.
|
57
|
-
- sell_amount: The amount of collateral to sell in atoms.
|
58
|
-
|
59
|
-
Returns:
|
60
|
-
- An OrderQuoteResponse containing the quote information.
|
61
|
-
|
62
|
-
Raises:
|
63
|
-
- NoLiquidityAvailableOnCowException if no liquidity is available on CoW.
|
64
|
-
"""
|
65
|
-
|
66
|
-
order_quote_request = OrderQuoteRequest(
|
67
|
-
buyToken=Address(buy_token),
|
68
|
-
sellToken=Address(collateral_token),
|
69
|
-
from_=Address(ADDRESS_ZERO),
|
70
|
-
)
|
71
|
-
|
72
|
-
order_side = OrderQuoteSide1(
|
73
|
-
kind=OrderQuoteSideKindSell.sell,
|
74
|
-
sellAmountBeforeFee=TokenAmountCow(str(sell_amount)),
|
75
|
-
)
|
76
|
-
try:
|
77
|
-
return asyncio.run(
|
78
|
-
get_order_quote(
|
79
|
-
order_quote_request=order_quote_request,
|
80
|
-
order_side=order_side,
|
81
|
-
order_book_api=self.order_book_api,
|
82
|
-
)
|
83
|
-
)
|
84
|
-
|
85
|
-
except UnexpectedResponseError as e1:
|
86
|
-
if "NoLiquidity" in e1.message:
|
87
|
-
raise NoLiquidityAvailableOnCowException(e1.message)
|
88
|
-
logger.warning(f"Found unexpected Cow response error: {e1}")
|
89
|
-
raise
|
90
|
-
except Exception as e:
|
91
|
-
logger.warning(f"Found unhandled Cow response error: {e}")
|
92
|
-
raise
|
93
|
-
|
94
|
-
@staticmethod
|
95
|
-
def swap(
|
96
|
-
amount: CollateralToken,
|
97
|
-
sell_token: ChecksumAddress,
|
98
|
-
buy_token: ChecksumAddress,
|
99
|
-
api_keys: APIKeys,
|
100
|
-
web3: Web3 | None = None,
|
101
|
-
) -> OrderMetaData:
|
102
|
-
order_metadata = swap_tokens_waiting(
|
103
|
-
amount_wei=amount.as_wei,
|
104
|
-
sell_token=sell_token,
|
105
|
-
buy_token=buy_token,
|
106
|
-
api_keys=api_keys,
|
107
|
-
web3=web3,
|
108
|
-
)
|
109
|
-
logger.debug(
|
110
|
-
f"Purchased {buy_token} in exchange for {sell_token}. Order details {order_metadata}"
|
111
|
-
)
|
112
|
-
return order_metadata
|
File without changes
|
File without changes
|