prediction-market-agent-tooling 0.61.1.dev489__py3-none-any.whl → 0.62.0__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 +4 -5
- prediction_market_agent_tooling/deploy/betting_strategy.py +53 -69
- prediction_market_agent_tooling/gtypes.py +105 -27
- prediction_market_agent_tooling/jobs/jobs_models.py +5 -7
- prediction_market_agent_tooling/jobs/omen/omen_jobs.py +13 -17
- prediction_market_agent_tooling/markets/agent_market.py +96 -55
- prediction_market_agent_tooling/markets/blockchain_utils.py +2 -32
- prediction_market_agent_tooling/markets/data_models.py +40 -44
- prediction_market_agent_tooling/markets/manifold/api.py +2 -6
- prediction_market_agent_tooling/markets/manifold/data_models.py +33 -25
- prediction_market_agent_tooling/markets/manifold/manifold.py +13 -11
- prediction_market_agent_tooling/markets/market_fees.py +6 -2
- prediction_market_agent_tooling/markets/omen/data_models.py +66 -57
- prediction_market_agent_tooling/markets/omen/omen.py +222 -250
- prediction_market_agent_tooling/markets/omen/omen_contracts.py +32 -33
- prediction_market_agent_tooling/markets/omen/omen_resolving.py +7 -14
- prediction_market_agent_tooling/markets/omen/omen_subgraph_handler.py +20 -14
- prediction_market_agent_tooling/markets/polymarket/data_models.py +3 -3
- prediction_market_agent_tooling/markets/polymarket/data_models_web.py +4 -4
- prediction_market_agent_tooling/markets/polymarket/polymarket.py +3 -5
- prediction_market_agent_tooling/markets/seer/data_models.py +92 -5
- prediction_market_agent_tooling/markets/seer/seer.py +93 -115
- prediction_market_agent_tooling/markets/seer/seer_contracts.py +11 -6
- prediction_market_agent_tooling/markets/seer/seer_subgraph_handler.py +18 -26
- prediction_market_agent_tooling/monitor/monitor.py +2 -2
- prediction_market_agent_tooling/tools/_generic_value.py +261 -0
- prediction_market_agent_tooling/tools/balances.py +14 -11
- prediction_market_agent_tooling/tools/betting_strategies/kelly_criterion.py +12 -10
- prediction_market_agent_tooling/tools/betting_strategies/market_moving.py +31 -24
- prediction_market_agent_tooling/tools/betting_strategies/utils.py +3 -1
- prediction_market_agent_tooling/tools/contract.py +14 -10
- prediction_market_agent_tooling/tools/cow/cow_manager.py +3 -4
- prediction_market_agent_tooling/tools/cow/cow_order.py +51 -7
- prediction_market_agent_tooling/tools/langfuse_client_utils.py +13 -1
- prediction_market_agent_tooling/tools/omen/sell_positions.py +6 -3
- prediction_market_agent_tooling/tools/safe.py +5 -6
- prediction_market_agent_tooling/tools/tokens/auto_deposit.py +36 -27
- prediction_market_agent_tooling/tools/tokens/auto_withdraw.py +4 -25
- prediction_market_agent_tooling/tools/tokens/main_token.py +2 -2
- prediction_market_agent_tooling/tools/tokens/token_utils.py +46 -0
- prediction_market_agent_tooling/tools/tokens/usd.py +79 -0
- prediction_market_agent_tooling/tools/utils.py +14 -8
- prediction_market_agent_tooling/tools/web3_utils.py +24 -41
- {prediction_market_agent_tooling-0.61.1.dev489.dist-info → prediction_market_agent_tooling-0.62.0.dist-info}/METADATA +2 -1
- {prediction_market_agent_tooling-0.61.1.dev489.dist-info → prediction_market_agent_tooling-0.62.0.dist-info}/RECORD +48 -47
- prediction_market_agent_tooling/markets/seer/price_manager.py +0 -111
- prediction_market_agent_tooling/markets/seer/subgraph_data_models.py +0 -57
- {prediction_market_agent_tooling-0.61.1.dev489.dist-info → prediction_market_agent_tooling-0.62.0.dist-info}/LICENSE +0 -0
- {prediction_market_agent_tooling-0.61.1.dev489.dist-info → prediction_market_agent_tooling-0.62.0.dist-info}/WHEEL +0 -0
- {prediction_market_agent_tooling-0.61.1.dev489.dist-info → prediction_market_agent_tooling-0.62.0.dist-info}/entry_points.txt +0 -0
@@ -14,6 +14,8 @@ from cowdao_cowpy.order_book.generated.model import (
|
|
14
14
|
OrderMetaData,
|
15
15
|
OrderQuoteRequest,
|
16
16
|
OrderQuoteSide1,
|
17
|
+
OrderQuoteSide3,
|
18
|
+
OrderQuoteSideKindBuy,
|
17
19
|
OrderQuoteSideKindSell,
|
18
20
|
OrderStatus,
|
19
21
|
TokenAmount,
|
@@ -21,27 +23,63 @@ from cowdao_cowpy.order_book.generated.model import (
|
|
21
23
|
from eth_account.signers.local import LocalAccount
|
22
24
|
from eth_typing.evm import ChecksumAddress
|
23
25
|
from web3 import Web3
|
24
|
-
from web3.types import Wei
|
25
26
|
|
26
27
|
from prediction_market_agent_tooling.config import APIKeys
|
27
|
-
from prediction_market_agent_tooling.gtypes import ChecksumAddress, Wei
|
28
|
+
from prediction_market_agent_tooling.gtypes import ChecksumAddress, Wei
|
28
29
|
from prediction_market_agent_tooling.loggers import logger
|
29
30
|
from prediction_market_agent_tooling.tools.contract import ContractERC20OnGnosisChain
|
30
31
|
from prediction_market_agent_tooling.tools.utils import utcnow
|
31
32
|
|
32
33
|
|
34
|
+
class OrderStatusError(Exception):
|
35
|
+
pass
|
36
|
+
|
37
|
+
|
33
38
|
def get_order_book_api(env: Envs, chain: Chain) -> OrderBookApi:
|
34
39
|
chain_id = SupportedChainId(chain.value[0])
|
35
40
|
return OrderBookApi(OrderBookAPIConfigFactory.get_config(env, chain_id))
|
36
41
|
|
37
42
|
|
43
|
+
@tenacity.retry(
|
44
|
+
stop=tenacity.stop_after_attempt(3),
|
45
|
+
wait=tenacity.wait_fixed(1),
|
46
|
+
after=lambda x: logger.debug(f"get_sell_token_amount failed, {x.attempt_number=}."),
|
47
|
+
)
|
48
|
+
def get_sell_token_amount(
|
49
|
+
buy_amount: Wei,
|
50
|
+
sell_token: ChecksumAddress,
|
51
|
+
buy_token: ChecksumAddress,
|
52
|
+
chain: Chain = Chain.GNOSIS,
|
53
|
+
env: Envs = "prod",
|
54
|
+
) -> Wei:
|
55
|
+
"""
|
56
|
+
Calculate how much of the sell_token is needed to obtain a specified amount of buy_token.
|
57
|
+
"""
|
58
|
+
order_book_api = get_order_book_api(env, chain)
|
59
|
+
order_quote_request = OrderQuoteRequest(
|
60
|
+
sellToken=Address(sell_token),
|
61
|
+
buyToken=Address(buy_token),
|
62
|
+
from_=Address(
|
63
|
+
"0x1234567890abcdef1234567890abcdef12345678"
|
64
|
+
), # Just random address, doesn't matter.
|
65
|
+
)
|
66
|
+
order_side = OrderQuoteSide3(
|
67
|
+
kind=OrderQuoteSideKindBuy.buy,
|
68
|
+
buyAmountAfterFee=TokenAmount(str(buy_amount)),
|
69
|
+
)
|
70
|
+
order_quote = asyncio.run(
|
71
|
+
order_book_api.post_quote(order_quote_request, order_side)
|
72
|
+
)
|
73
|
+
return Wei(order_quote.quote.sellAmount.root)
|
74
|
+
|
75
|
+
|
38
76
|
@tenacity.retry(
|
39
77
|
stop=tenacity.stop_after_attempt(3),
|
40
78
|
wait=tenacity.wait_fixed(1),
|
41
79
|
after=lambda x: logger.debug(f"get_buy_token_amount failed, {x.attempt_number=}."),
|
42
80
|
)
|
43
81
|
def get_buy_token_amount(
|
44
|
-
|
82
|
+
sell_amount: Wei,
|
45
83
|
sell_token: ChecksumAddress,
|
46
84
|
buy_token: ChecksumAddress,
|
47
85
|
chain: Chain = Chain.GNOSIS,
|
@@ -57,14 +95,20 @@ def get_buy_token_amount(
|
|
57
95
|
)
|
58
96
|
order_side = OrderQuoteSide1(
|
59
97
|
kind=OrderQuoteSideKindSell.sell,
|
60
|
-
sellAmountBeforeFee=TokenAmount(str(
|
98
|
+
sellAmountBeforeFee=TokenAmount(str(sell_amount)),
|
61
99
|
)
|
62
100
|
order_quote = asyncio.run(
|
63
101
|
order_book_api.post_quote(order_quote_request, order_side)
|
64
102
|
)
|
65
|
-
return
|
103
|
+
return Wei(order_quote.quote.buyAmount.root)
|
66
104
|
|
67
105
|
|
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
|
+
)
|
68
112
|
def swap_tokens_waiting(
|
69
113
|
amount_wei: Wei,
|
70
114
|
sell_token: ChecksumAddress,
|
@@ -102,7 +146,7 @@ async def swap_tokens_waiting_async(
|
|
102
146
|
timeout: timedelta = timedelta(seconds=60),
|
103
147
|
) -> OrderMetaData:
|
104
148
|
order = await swap_tokens(
|
105
|
-
amount=amount_wei,
|
149
|
+
amount=amount_wei.value,
|
106
150
|
sell_token=sell_token,
|
107
151
|
buy_token=buy_token,
|
108
152
|
account=account,
|
@@ -125,7 +169,7 @@ async def swap_tokens_waiting_async(
|
|
125
169
|
OrderStatus.cancelled,
|
126
170
|
OrderStatus.expired,
|
127
171
|
):
|
128
|
-
raise
|
172
|
+
raise OrderStatusError(f"Order {order.uid} failed. {order.url}")
|
129
173
|
|
130
174
|
if utcnow() - start_time > timeout:
|
131
175
|
raise TimeoutError(
|
@@ -13,6 +13,9 @@ from prediction_market_agent_tooling.markets.data_models import (
|
|
13
13
|
TradeType,
|
14
14
|
)
|
15
15
|
from prediction_market_agent_tooling.markets.omen.omen import OmenAgentMarket
|
16
|
+
from prediction_market_agent_tooling.markets.omen.omen_constants import (
|
17
|
+
WRAPPED_XDAI_CONTRACT_ADDRESS,
|
18
|
+
)
|
16
19
|
from prediction_market_agent_tooling.tools.utils import DatetimeUTC
|
17
20
|
|
18
21
|
|
@@ -147,11 +150,20 @@ def get_trace_for_bet(
|
|
147
150
|
# Filter for traces with the same bet outcome and amount
|
148
151
|
traces_for_bet: list[ProcessMarketTrace] = []
|
149
152
|
for t in traces:
|
153
|
+
if (
|
154
|
+
t.market.collateral_token_contract_address_checksummed
|
155
|
+
not in WRAPPED_XDAI_CONTRACT_ADDRESS
|
156
|
+
):
|
157
|
+
# TODO: We need to compute bet amount token in USD here, but at the time of bet placement!
|
158
|
+
logger.warning(
|
159
|
+
"This currently works only for WXDAI markets, because we need to compare against USD value."
|
160
|
+
)
|
161
|
+
continue
|
150
162
|
# Cannot use exact comparison due to gas fees
|
151
163
|
if (
|
152
164
|
t.buy_trade
|
153
165
|
and t.buy_trade.outcome == bet.outcome
|
154
|
-
and np.isclose(t.buy_trade.amount.
|
166
|
+
and np.isclose(t.buy_trade.amount.value, bet.amount.value)
|
155
167
|
):
|
156
168
|
traces_for_bet.append(t)
|
157
169
|
|
@@ -1,6 +1,7 @@
|
|
1
1
|
from datetime import timedelta
|
2
2
|
|
3
3
|
from prediction_market_agent_tooling.config import APIKeys
|
4
|
+
from prediction_market_agent_tooling.gtypes import USD
|
4
5
|
from prediction_market_agent_tooling.loggers import logger
|
5
6
|
from prediction_market_agent_tooling.markets.omen.omen import OmenAgentMarket
|
6
7
|
from prediction_market_agent_tooling.markets.omen.omen_subgraph_handler import (
|
@@ -23,7 +24,9 @@ def sell_all(
|
|
23
24
|
better_address=better_address,
|
24
25
|
market_opening_after=utcnow() + timedelta(days=closing_later_than_days),
|
25
26
|
)
|
26
|
-
bets_total_usd = sum(
|
27
|
+
bets_total_usd = sum(
|
28
|
+
(b.get_collateral_amount_usd() for b in bets), start=USD.zero()
|
29
|
+
)
|
27
30
|
unique_market_urls = set(b.fpmm.url for b in bets)
|
28
31
|
starting_balance = get_balances(better_address)
|
29
32
|
new_balance = starting_balance
|
@@ -37,9 +40,9 @@ def sell_all(
|
|
37
40
|
outcome = agent_market.outcomes[bet.outcomeIndex]
|
38
41
|
current_token_balance = agent_market.get_token_balance(better_address, outcome)
|
39
42
|
|
40
|
-
if current_token_balance.
|
43
|
+
if current_token_balance.as_token <= agent_market.get_tiny_bet_amount():
|
41
44
|
logger.info(
|
42
|
-
f"Skipping bet on {bet.fpmm.url} because the actual balance is unreasonably low {current_token_balance
|
45
|
+
f"Skipping bet on {bet.fpmm.url} because the actual balance is unreasonably low {current_token_balance}."
|
43
46
|
)
|
44
47
|
continue
|
45
48
|
|
@@ -11,11 +11,10 @@ from safe_eth.eth.constants import NULL_ADDRESS
|
|
11
11
|
from safe_eth.eth.contracts import get_safe_V1_4_1_contract
|
12
12
|
from safe_eth.safe.proxy_factory import ProxyFactoryV141
|
13
13
|
from safe_eth.safe.safe import SafeV141
|
14
|
-
from web3.types import Wei
|
15
14
|
|
15
|
+
from prediction_market_agent_tooling.gtypes import Wei
|
16
16
|
from prediction_market_agent_tooling.loggers import logger
|
17
17
|
from prediction_market_agent_tooling.tools.hexbytes_custom import HexBytes
|
18
|
-
from prediction_market_agent_tooling.tools.web3_utils import wei_to_xdai
|
19
18
|
|
20
19
|
|
21
20
|
def create_safe(
|
@@ -63,10 +62,10 @@ def create_safe(
|
|
63
62
|
f"does not exist on network {ethereum_network.name}"
|
64
63
|
)
|
65
64
|
|
66
|
-
account_balance = ethereum_client.get_balance(account.address)
|
67
|
-
account_balance_xdai =
|
65
|
+
account_balance = Wei(ethereum_client.get_balance(account.address))
|
66
|
+
account_balance_xdai = account_balance.as_token
|
68
67
|
# We set a reasonable expected balance below for Safe deployment not to fail.
|
69
|
-
if account_balance_xdai < 0.01:
|
68
|
+
if account_balance_xdai.value < 0.01:
|
70
69
|
raise ValueError(
|
71
70
|
f"Client's balance is {account_balance_xdai} xDAI, too low for deploying a Safe."
|
72
71
|
)
|
@@ -108,7 +107,7 @@ def create_safe(
|
|
108
107
|
payment_token,
|
109
108
|
payment,
|
110
109
|
payment_receiver,
|
111
|
-
).build_transaction({"gas": 1, "gasPrice": Wei(1)})["data"]
|
110
|
+
).build_transaction({"gas": 1, "gasPrice": Wei(1).value})["data"]
|
112
111
|
)
|
113
112
|
|
114
113
|
proxy_factory = ProxyFactoryV141(proxy_factory_address, ethereum_client)
|
@@ -1,7 +1,7 @@
|
|
1
1
|
from web3 import Web3
|
2
2
|
|
3
3
|
from prediction_market_agent_tooling.config import APIKeys
|
4
|
-
from prediction_market_agent_tooling.gtypes import
|
4
|
+
from prediction_market_agent_tooling.gtypes import USD, Wei
|
5
5
|
from prediction_market_agent_tooling.loggers import logger
|
6
6
|
from prediction_market_agent_tooling.tools.contract import (
|
7
7
|
ContractDepositableWrapperERC20BaseClass,
|
@@ -10,31 +10,46 @@ from prediction_market_agent_tooling.tools.contract import (
|
|
10
10
|
ContractERC4626BaseClass,
|
11
11
|
)
|
12
12
|
from prediction_market_agent_tooling.tools.cow.cow_order import (
|
13
|
-
|
13
|
+
get_sell_token_amount,
|
14
14
|
swap_tokens_waiting,
|
15
15
|
)
|
16
16
|
from prediction_market_agent_tooling.tools.tokens.main_token import KEEPING_ERC20_TOKEN
|
17
|
+
from prediction_market_agent_tooling.tools.tokens.usd import get_usd_in_token
|
17
18
|
from prediction_market_agent_tooling.tools.utils import should_not_happen
|
18
|
-
from prediction_market_agent_tooling.tools.web3_utils import wei_to_xdai
|
19
19
|
|
20
20
|
|
21
21
|
def auto_deposit_collateral_token(
|
22
22
|
collateral_token_contract: ContractERC20BaseClass,
|
23
|
-
|
23
|
+
collateral_amount_wei_or_usd: Wei | USD,
|
24
24
|
api_keys: APIKeys,
|
25
25
|
web3: Web3 | None = None,
|
26
|
+
surplus: float = 0.01,
|
26
27
|
) -> None:
|
28
|
+
collateral_amount_wei = (
|
29
|
+
collateral_amount_wei_or_usd
|
30
|
+
if isinstance(collateral_amount_wei_or_usd, Wei)
|
31
|
+
else get_usd_in_token(
|
32
|
+
collateral_amount_wei_or_usd, collateral_token_contract.address
|
33
|
+
).as_wei
|
34
|
+
)
|
35
|
+
# Deposit a bit more to cover any small changes in conversions.
|
36
|
+
collateral_amount_wei = collateral_amount_wei.with_fraction(surplus)
|
37
|
+
|
27
38
|
if isinstance(collateral_token_contract, ContractDepositableWrapperERC20BaseClass):
|
28
39
|
# In this case, we can use deposit function directly, no need to go through DEX.
|
29
40
|
auto_deposit_depositable_wrapper_erc20(
|
30
|
-
collateral_token_contract,
|
41
|
+
collateral_token_contract, collateral_amount_wei, api_keys, web3
|
31
42
|
)
|
32
43
|
|
33
44
|
elif isinstance(collateral_token_contract, ContractERC4626BaseClass):
|
34
|
-
auto_deposit_erc4626(
|
45
|
+
auto_deposit_erc4626(
|
46
|
+
collateral_token_contract, collateral_amount_wei, api_keys, web3
|
47
|
+
)
|
35
48
|
|
36
49
|
elif isinstance(collateral_token_contract, ContractERC20BaseClass):
|
37
|
-
auto_deposit_erc20(
|
50
|
+
auto_deposit_erc20(
|
51
|
+
collateral_token_contract, collateral_amount_wei, api_keys, web3
|
52
|
+
)
|
38
53
|
|
39
54
|
else:
|
40
55
|
should_not_happen("Unsupported ERC20 contract type.")
|
@@ -55,16 +70,16 @@ def auto_deposit_depositable_wrapper_erc20(
|
|
55
70
|
return
|
56
71
|
|
57
72
|
# If we don't have enough, we need to deposit the difference.
|
58
|
-
left_to_deposit =
|
73
|
+
left_to_deposit = amount_wei - collateral_token_balance
|
59
74
|
logger.info(
|
60
|
-
f"Depositing {
|
75
|
+
f"Depositing {left_to_deposit.as_token} {collateral_token_contract.symbol()}."
|
61
76
|
)
|
62
77
|
collateral_token_contract.deposit(api_keys, left_to_deposit, web3=web3)
|
63
78
|
|
64
79
|
|
65
80
|
def auto_deposit_erc4626(
|
66
81
|
collateral_token_contract: ContractERC4626BaseClass,
|
67
|
-
|
82
|
+
collateral_amount_wei: Wei,
|
68
83
|
api_keys: APIKeys,
|
69
84
|
web3: Web3 | None,
|
70
85
|
) -> None:
|
@@ -72,12 +87,12 @@ def auto_deposit_erc4626(
|
|
72
87
|
collateral_token_balance_in_shares = collateral_token_contract.balanceOf(
|
73
88
|
for_address=for_address, web3=web3
|
74
89
|
)
|
75
|
-
|
76
|
-
|
90
|
+
asset_amount_wei = collateral_token_contract.convertToAssets(
|
91
|
+
collateral_amount_wei, web3
|
77
92
|
)
|
78
93
|
|
79
94
|
# If we have enough shares, we don't need to deposit.
|
80
|
-
if collateral_token_balance_in_shares >=
|
95
|
+
if collateral_token_balance_in_shares >= collateral_amount_wei:
|
81
96
|
return
|
82
97
|
|
83
98
|
# If we need to deposit into erc4626, we first need to have enough of the asset token.
|
@@ -89,7 +104,7 @@ def auto_deposit_erc4626(
|
|
89
104
|
collateral_token_balance_in_assets = collateral_token_contract.convertToAssets(
|
90
105
|
collateral_token_balance_in_shares, web3
|
91
106
|
)
|
92
|
-
left_to_deposit =
|
107
|
+
left_to_deposit = asset_amount_wei - collateral_token_balance_in_assets
|
93
108
|
if (
|
94
109
|
collateral_token_contract.get_asset_token_balance(for_address, web3)
|
95
110
|
< left_to_deposit
|
@@ -108,31 +123,25 @@ def auto_deposit_erc4626(
|
|
108
123
|
|
109
124
|
def auto_deposit_erc20(
|
110
125
|
collateral_token_contract: ContractERC20BaseClass,
|
111
|
-
|
126
|
+
collateral_amount_wei: Wei,
|
112
127
|
api_keys: APIKeys,
|
113
128
|
web3: Web3 | None,
|
114
129
|
) -> None:
|
115
|
-
# How much it is in the other token (collateral token).
|
116
|
-
collateral_amount_wei = get_buy_token_amount(
|
117
|
-
amount_xdai_wei,
|
118
|
-
KEEPING_ERC20_TOKEN.address,
|
119
|
-
collateral_token_contract.address,
|
120
|
-
)
|
121
130
|
# How much do we have already in the other token (collateral token).
|
122
131
|
collateral_balance_wei = collateral_token_contract.balanceOf(
|
123
132
|
api_keys.bet_from_address
|
124
133
|
)
|
125
134
|
# Amount of collateral token remaining to get.
|
126
135
|
remaining_to_get_in_collateral_wei = max(
|
127
|
-
0, collateral_amount_wei - collateral_balance_wei
|
136
|
+
Wei(0), collateral_amount_wei - collateral_balance_wei
|
128
137
|
)
|
129
138
|
if not remaining_to_get_in_collateral_wei:
|
130
139
|
return
|
131
|
-
#
|
132
|
-
amount_to_sell_wei =
|
133
|
-
|
134
|
-
|
135
|
-
|
140
|
+
# Get of how much of the source token we need to sell in order to fill the remaining collateral amount.
|
141
|
+
amount_to_sell_wei = get_sell_token_amount(
|
142
|
+
remaining_to_get_in_collateral_wei,
|
143
|
+
sell_token=KEEPING_ERC20_TOKEN.address,
|
144
|
+
buy_token=collateral_token_contract.address,
|
136
145
|
)
|
137
146
|
# If we don't have enough of the source token.
|
138
147
|
if amount_to_sell_wei > ContractERC20OnGnosisChain(
|
@@ -7,16 +7,9 @@ from prediction_market_agent_tooling.tools.contract import (
|
|
7
7
|
ContractERC20BaseClass,
|
8
8
|
ContractERC4626BaseClass,
|
9
9
|
)
|
10
|
-
from prediction_market_agent_tooling.tools.cow.cow_order import
|
11
|
-
get_buy_token_amount,
|
12
|
-
swap_tokens_waiting,
|
13
|
-
)
|
10
|
+
from prediction_market_agent_tooling.tools.cow.cow_order import swap_tokens_waiting
|
14
11
|
from prediction_market_agent_tooling.tools.tokens.main_token import KEEPING_ERC20_TOKEN
|
15
12
|
from prediction_market_agent_tooling.tools.utils import should_not_happen
|
16
|
-
from prediction_market_agent_tooling.tools.web3_utils import (
|
17
|
-
remove_fraction,
|
18
|
-
wei_to_xdai,
|
19
|
-
)
|
20
13
|
|
21
14
|
|
22
15
|
def auto_withdraw_collateral_token(
|
@@ -24,14 +17,7 @@ def auto_withdraw_collateral_token(
|
|
24
17
|
amount_wei: Wei,
|
25
18
|
api_keys: APIKeys,
|
26
19
|
web3: Web3 | None = None,
|
27
|
-
slippage: float = 0.001,
|
28
20
|
) -> None:
|
29
|
-
# Small slippage as exact exchange rate constantly changes and we don't care about small differences.
|
30
|
-
amount_wei = remove_fraction(
|
31
|
-
amount_wei,
|
32
|
-
slippage,
|
33
|
-
)
|
34
|
-
|
35
21
|
if not amount_wei:
|
36
22
|
logger.warning(
|
37
23
|
f"Amount to withdraw is zero, skipping withdrawal of {collateral_token_contract.symbol_cached(web3)}."
|
@@ -51,7 +37,7 @@ def auto_withdraw_collateral_token(
|
|
51
37
|
):
|
52
38
|
# If the ERC4626 is backed by KEEPING_ERC20_TOKEN, we can withdraw it directly, no need to go through DEX.
|
53
39
|
logger.info(
|
54
|
-
f"Withdrawing {
|
40
|
+
f"Withdrawing {amount_wei.as_token} from {collateral_token_contract.symbol_cached(web3)} into {KEEPING_ERC20_TOKEN.symbol_cached(web3)}"
|
55
41
|
)
|
56
42
|
collateral_token_contract.withdraw_in_shares(
|
57
43
|
api_keys,
|
@@ -60,18 +46,11 @@ def auto_withdraw_collateral_token(
|
|
60
46
|
)
|
61
47
|
elif isinstance(collateral_token_contract, ContractERC20BaseClass):
|
62
48
|
logger.info(
|
63
|
-
f"Swapping {
|
49
|
+
f"Swapping {amount_wei.as_token} from {collateral_token_contract.symbol_cached(web3)} into {KEEPING_ERC20_TOKEN.symbol_cached(web3)}"
|
64
50
|
)
|
65
51
|
# Otherwise, DEX will handle the rest of token swaps.
|
66
|
-
# First, convert `amount_wei` from xDai-based value into the collateral token-based value.
|
67
|
-
collateral_amount_wei = get_buy_token_amount(
|
68
|
-
amount_wei,
|
69
|
-
KEEPING_ERC20_TOKEN.address,
|
70
|
-
collateral_token_contract.address,
|
71
|
-
)
|
72
|
-
# And then sell it.
|
73
52
|
swap_tokens_waiting(
|
74
|
-
amount_wei=
|
53
|
+
amount_wei=amount_wei,
|
75
54
|
sell_token=collateral_token_contract.address,
|
76
55
|
buy_token=KEEPING_ERC20_TOKEN.address,
|
77
56
|
api_keys=api_keys,
|
@@ -1,4 +1,4 @@
|
|
1
|
-
from prediction_market_agent_tooling.gtypes import
|
1
|
+
from prediction_market_agent_tooling.gtypes import xDai
|
2
2
|
from prediction_market_agent_tooling.markets.omen.omen_constants import (
|
3
3
|
WRAPPED_XDAI_CONTRACT_ADDRESS,
|
4
4
|
)
|
@@ -15,4 +15,4 @@ KEEPING_ERC20_TOKEN = ContractDepositableWrapperERC20OnGnosisChain(
|
|
15
15
|
address=WRAPPED_XDAI_CONTRACT_ADDRESS
|
16
16
|
)
|
17
17
|
|
18
|
-
MINIMUM_NATIVE_TOKEN_IN_EOA_FOR_FEES =
|
18
|
+
MINIMUM_NATIVE_TOKEN_IN_EOA_FOR_FEES = xDai(0.1)
|
@@ -0,0 +1,46 @@
|
|
1
|
+
from eth_typing.evm import ChecksumAddress
|
2
|
+
from web3 import Web3
|
3
|
+
|
4
|
+
from prediction_market_agent_tooling.gtypes import ChecksumAddress, Wei
|
5
|
+
from prediction_market_agent_tooling.tools.contract import (
|
6
|
+
ContractERC4626BaseClass,
|
7
|
+
init_collateral_token_contract,
|
8
|
+
to_gnosis_chain_contract,
|
9
|
+
)
|
10
|
+
from prediction_market_agent_tooling.tools.cow.cow_order import get_buy_token_amount
|
11
|
+
|
12
|
+
|
13
|
+
def convert_to_another_token(
|
14
|
+
amount: Wei,
|
15
|
+
from_token: ChecksumAddress,
|
16
|
+
to_token: ChecksumAddress,
|
17
|
+
web3: Web3 | None = None,
|
18
|
+
) -> Wei:
|
19
|
+
from_token_contract = to_gnosis_chain_contract(
|
20
|
+
init_collateral_token_contract(from_token, web3)
|
21
|
+
)
|
22
|
+
to_token_contract = to_gnosis_chain_contract(
|
23
|
+
init_collateral_token_contract(to_token, web3)
|
24
|
+
)
|
25
|
+
|
26
|
+
if from_token == to_token:
|
27
|
+
return amount
|
28
|
+
|
29
|
+
elif (
|
30
|
+
isinstance(to_token_contract, ContractERC4626BaseClass)
|
31
|
+
and to_token_contract.get_asset_token_contract().address == from_token
|
32
|
+
):
|
33
|
+
return to_token_contract.convertToShares(amount)
|
34
|
+
|
35
|
+
elif (
|
36
|
+
isinstance(from_token_contract, ContractERC4626BaseClass)
|
37
|
+
and from_token_contract.get_asset_token_contract().address == to_token
|
38
|
+
):
|
39
|
+
return from_token_contract.convertToAssets(amount)
|
40
|
+
|
41
|
+
else:
|
42
|
+
return get_buy_token_amount(
|
43
|
+
amount,
|
44
|
+
from_token,
|
45
|
+
to_token,
|
46
|
+
)
|
@@ -0,0 +1,79 @@
|
|
1
|
+
from cachetools import TTLCache, cached
|
2
|
+
from eth_typing.evm import ChecksumAddress
|
3
|
+
|
4
|
+
from prediction_market_agent_tooling.gtypes import (
|
5
|
+
USD,
|
6
|
+
ChecksumAddress,
|
7
|
+
CollateralToken,
|
8
|
+
xDai,
|
9
|
+
)
|
10
|
+
from prediction_market_agent_tooling.markets.omen.omen_constants import (
|
11
|
+
SDAI_CONTRACT_ADDRESS,
|
12
|
+
WRAPPED_XDAI_CONTRACT_ADDRESS,
|
13
|
+
)
|
14
|
+
from prediction_market_agent_tooling.tools.contract import ContractERC4626OnGnosisChain
|
15
|
+
from prediction_market_agent_tooling.tools.cow.cow_order import get_buy_token_amount
|
16
|
+
|
17
|
+
|
18
|
+
def get_usd_in_xdai(amount: USD) -> xDai:
|
19
|
+
# xDai is stable coin against USD, so for simplicity we just cast it.
|
20
|
+
return xDai(amount.value)
|
21
|
+
|
22
|
+
|
23
|
+
def get_xdai_in_usd(amount: xDai) -> USD:
|
24
|
+
# xDai is stable coin against USD, so for simplicity we just cast it.
|
25
|
+
return USD(amount.value)
|
26
|
+
|
27
|
+
|
28
|
+
def get_usd_in_token(amount: USD, token_address: ChecksumAddress) -> CollateralToken:
|
29
|
+
rate = get_single_usd_to_token_rate(token_address)
|
30
|
+
return CollateralToken(amount.value * rate.value)
|
31
|
+
|
32
|
+
|
33
|
+
def get_token_in_usd(amount: CollateralToken, token_address: ChecksumAddress) -> USD:
|
34
|
+
rate = get_single_token_to_usd_rate(token_address)
|
35
|
+
return USD(amount.value * rate.value)
|
36
|
+
|
37
|
+
|
38
|
+
# A short cache to not spam CoW and prevent timeouts, but still have relatively fresh data.
|
39
|
+
@cached(TTLCache(maxsize=100, ttl=5 * 60))
|
40
|
+
def get_single_token_to_usd_rate(token_address: ChecksumAddress) -> USD:
|
41
|
+
# (w)xDai is a stable coin against USD, so use it to estimate USD worth.
|
42
|
+
if WRAPPED_XDAI_CONTRACT_ADDRESS == token_address:
|
43
|
+
return USD(1.0)
|
44
|
+
# sDai is ERC4626 with wxDai as asset, we can take the rate directly from there instead of calling CoW.
|
45
|
+
if SDAI_CONTRACT_ADDRESS == token_address:
|
46
|
+
return USD(
|
47
|
+
ContractERC4626OnGnosisChain(address=SDAI_CONTRACT_ADDRESS)
|
48
|
+
.convertToAssets(CollateralToken(1).as_wei)
|
49
|
+
.as_token.value
|
50
|
+
)
|
51
|
+
in_wei = get_buy_token_amount(
|
52
|
+
sell_amount=CollateralToken(1).as_wei,
|
53
|
+
sell_token=token_address,
|
54
|
+
buy_token=WRAPPED_XDAI_CONTRACT_ADDRESS,
|
55
|
+
)
|
56
|
+
in_token = in_wei.as_token
|
57
|
+
return USD(in_token.value)
|
58
|
+
|
59
|
+
|
60
|
+
# A short cache to not spam CoW and prevent timeouts, but still have relatively fresh data.
|
61
|
+
@cached(TTLCache(maxsize=100, ttl=5 * 60))
|
62
|
+
def get_single_usd_to_token_rate(token_address: ChecksumAddress) -> CollateralToken:
|
63
|
+
# (w)xDai is a stable coin against USD, so use it to estimate USD worth.
|
64
|
+
if WRAPPED_XDAI_CONTRACT_ADDRESS == token_address:
|
65
|
+
return CollateralToken(1.0)
|
66
|
+
# sDai is ERC4626 with wxDai as asset, we can take the rate directly from there instead of calling CoW.
|
67
|
+
if SDAI_CONTRACT_ADDRESS == token_address:
|
68
|
+
return CollateralToken(
|
69
|
+
ContractERC4626OnGnosisChain(address=SDAI_CONTRACT_ADDRESS)
|
70
|
+
.convertToShares(CollateralToken(1).as_wei)
|
71
|
+
.as_token.value
|
72
|
+
)
|
73
|
+
in_wei = get_buy_token_amount(
|
74
|
+
sell_amount=CollateralToken(1).as_wei,
|
75
|
+
sell_token=WRAPPED_XDAI_CONTRACT_ADDRESS,
|
76
|
+
buy_token=token_address,
|
77
|
+
)
|
78
|
+
in_token = in_wei.as_token
|
79
|
+
return CollateralToken(in_token.value)
|
@@ -9,7 +9,13 @@ from pydantic import BaseModel, ValidationError
|
|
9
9
|
from scipy.optimize import newton
|
10
10
|
from scipy.stats import entropy
|
11
11
|
|
12
|
-
from prediction_market_agent_tooling.gtypes import
|
12
|
+
from prediction_market_agent_tooling.gtypes import (
|
13
|
+
CollateralToken,
|
14
|
+
DatetimeUTC,
|
15
|
+
OutcomeToken,
|
16
|
+
Probability,
|
17
|
+
SecretStr,
|
18
|
+
)
|
13
19
|
from prediction_market_agent_tooling.loggers import logger
|
14
20
|
from prediction_market_agent_tooling.markets.market_fees import MarketFees
|
15
21
|
|
@@ -182,11 +188,11 @@ def prob_uncertainty(prob: Probability) -> float:
|
|
182
188
|
|
183
189
|
|
184
190
|
def calculate_sell_amount_in_collateral(
|
185
|
-
shares_to_sell:
|
186
|
-
holdings:
|
187
|
-
other_holdings:
|
191
|
+
shares_to_sell: OutcomeToken,
|
192
|
+
holdings: OutcomeToken,
|
193
|
+
other_holdings: OutcomeToken,
|
188
194
|
fees: MarketFees,
|
189
|
-
) ->
|
195
|
+
) -> CollateralToken:
|
190
196
|
"""
|
191
197
|
Computes the amount of collateral that needs to be sold to get `shares`
|
192
198
|
amount of shares. Returns None if the amount can't be computed.
|
@@ -199,11 +205,11 @@ def calculate_sell_amount_in_collateral(
|
|
199
205
|
raise ValueError("All share args must be greater than 0")
|
200
206
|
|
201
207
|
def f(r: float) -> float:
|
202
|
-
R = (r + fees.absolute) / (1 - fees.bet_proportion)
|
208
|
+
R = OutcomeToken((r + fees.absolute) / (1 - fees.bet_proportion))
|
203
209
|
first_term = other_holdings - R
|
204
210
|
second_term = holdings + shares_to_sell - R
|
205
211
|
third_term = holdings * other_holdings
|
206
|
-
return (first_term * second_term) - third_term
|
212
|
+
return ((first_term * second_term) - third_term).value
|
207
213
|
|
208
214
|
amount_to_sell = newton(f, 0)
|
209
|
-
return float(amount_to_sell) * 0.999999 # Avoid rounding errors
|
215
|
+
return CollateralToken(float(amount_to_sell) * 0.999999) # Avoid rounding errors
|