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
@@ -0,0 +1,261 @@
|
|
1
|
+
import typing as t
|
2
|
+
from decimal import Decimal
|
3
|
+
from typing import TypeVar, overload
|
4
|
+
|
5
|
+
from pydantic import GetCoreSchemaHandler
|
6
|
+
from pydantic_core import CoreSchema, core_schema
|
7
|
+
from web3.types import Wei as WeiWeb3
|
8
|
+
|
9
|
+
InputValueType = TypeVar(
|
10
|
+
"InputValueType", bound=t.Union[str, int, float, WeiWeb3, Decimal]
|
11
|
+
)
|
12
|
+
InternalValueType = TypeVar("InternalValueType", bound=t.Union[int, float, WeiWeb3])
|
13
|
+
|
14
|
+
|
15
|
+
class _GenericValue(
|
16
|
+
t.Generic[InputValueType, InternalValueType],
|
17
|
+
# Not great, but it allows to serialize this object with plain json.
|
18
|
+
dict[t.Literal["value"] | t.Literal["type"], InternalValueType | str],
|
19
|
+
):
|
20
|
+
"""
|
21
|
+
A helper class intended for inheritance. Do not instantiate this class directly.
|
22
|
+
|
23
|
+
Example:
|
24
|
+
|
25
|
+
```python
|
26
|
+
a = _GenericValue(10)
|
27
|
+
b = Token(100) # Token is a subclass of _GenericValue
|
28
|
+
c = xDai(100) # xDai is a subclass of _GenericValue
|
29
|
+
d = Mana(100) # Mana is a subclass of _GenericValue
|
30
|
+
e = xDai(50)
|
31
|
+
|
32
|
+
# Mypy will complain if we try to work with different currencies (types)
|
33
|
+
b - c # mypy will report incompatible types
|
34
|
+
c - d # mypy will report incompatible types
|
35
|
+
c - e # mypy will be ok
|
36
|
+
a - b # mypy won't report issues, as others are subclasses of _GenericValue, and that's a problem, so don't use _GenericValue directly
|
37
|
+
|
38
|
+
# Resulting types after arithmetic operations are as expected, so we don't need to wrap them as before (e.g. xdai_type(c + c))
|
39
|
+
x = c - e # x is of type xDai
|
40
|
+
x = c * e # x if of type xDai
|
41
|
+
x = c / e # x is of type float (pure value after division with same types)
|
42
|
+
x = c / 2 # x is of type xDai
|
43
|
+
x = c // 2 # x is of type xDai
|
44
|
+
x * x * 2 # x is of type xDai
|
45
|
+
```
|
46
|
+
|
47
|
+
TODO: There are some type ignores which isn't cool, but it works and type-wise values are also correct. Idk how to explain it to mypy though.
|
48
|
+
"""
|
49
|
+
|
50
|
+
GenericValueType = TypeVar(
|
51
|
+
"GenericValueType", bound="_GenericValue[InputValueType, InternalValueType]"
|
52
|
+
)
|
53
|
+
|
54
|
+
parser: t.Callable[[InputValueType], InternalValueType]
|
55
|
+
|
56
|
+
def __init_subclass__(
|
57
|
+
cls, parser: t.Callable[[InputValueType], InternalValueType]
|
58
|
+
) -> None:
|
59
|
+
super().__init_subclass__()
|
60
|
+
cls.parser = parser
|
61
|
+
|
62
|
+
def __init__(self, value: InputValueType) -> None:
|
63
|
+
self.value: InternalValueType = self.parser(value)
|
64
|
+
super().__init__({"value": self.value, "type": self.__class__.__name__})
|
65
|
+
|
66
|
+
def __str__(self) -> str:
|
67
|
+
return f"{self.value}"
|
68
|
+
|
69
|
+
def __neg__(self: GenericValueType) -> GenericValueType:
|
70
|
+
return type(self)(-self.value) # type: ignore[arg-type]
|
71
|
+
|
72
|
+
def __abs__(self: GenericValueType) -> GenericValueType:
|
73
|
+
return type(self)(abs(self.value)) # type: ignore[arg-type]
|
74
|
+
|
75
|
+
def __sub__(
|
76
|
+
self: GenericValueType, other: GenericValueType | t.Literal[0]
|
77
|
+
) -> GenericValueType:
|
78
|
+
if other == 0:
|
79
|
+
other = self.zero()
|
80
|
+
if not isinstance(other, _GenericValue):
|
81
|
+
raise TypeError("Cannot subtract different types")
|
82
|
+
if type(self) is not type(other):
|
83
|
+
raise TypeError("Cannot subtract different types")
|
84
|
+
return type(self)(self.value - other.value)
|
85
|
+
|
86
|
+
def __add__(
|
87
|
+
self: GenericValueType, other: GenericValueType | t.Literal[0]
|
88
|
+
) -> GenericValueType:
|
89
|
+
if other == 0:
|
90
|
+
other = self.zero()
|
91
|
+
if not isinstance(other, _GenericValue):
|
92
|
+
raise TypeError("Cannot add different types")
|
93
|
+
if type(self) is not type(other):
|
94
|
+
raise TypeError("Cannot add different types")
|
95
|
+
return type(self)(self.value + other.value)
|
96
|
+
|
97
|
+
def __mul__(
|
98
|
+
self: GenericValueType, other: GenericValueType | int | float
|
99
|
+
) -> GenericValueType:
|
100
|
+
if not isinstance(other, (_GenericValue, int, float)):
|
101
|
+
raise TypeError("Cannot multiply different types")
|
102
|
+
if not isinstance(other, (int, float)) and type(self) is not type(other):
|
103
|
+
raise TypeError("Cannot multiply different types")
|
104
|
+
return type(self)(self.value * (other if isinstance(other, (int, float)) else other.value)) # type: ignore
|
105
|
+
|
106
|
+
@overload
|
107
|
+
def __truediv__(self: GenericValueType, other: int | float) -> GenericValueType:
|
108
|
+
...
|
109
|
+
|
110
|
+
@overload
|
111
|
+
def __truediv__(
|
112
|
+
self: GenericValueType, other: GenericValueType
|
113
|
+
) -> InternalValueType:
|
114
|
+
...
|
115
|
+
|
116
|
+
def __truediv__(
|
117
|
+
self: GenericValueType, other: GenericValueType | int | float
|
118
|
+
) -> GenericValueType | InternalValueType:
|
119
|
+
if not isinstance(other, (_GenericValue, int, float)):
|
120
|
+
raise TypeError("Cannot multiply different types")
|
121
|
+
if not isinstance(other, (int, float)) and type(self) is not type(other):
|
122
|
+
raise TypeError("Cannot multiply different types")
|
123
|
+
if other == 0:
|
124
|
+
raise ZeroDivisionError("Cannot divide by zero")
|
125
|
+
if isinstance(other, (int, float)):
|
126
|
+
return type(self)(self.value / other) # type: ignore
|
127
|
+
else:
|
128
|
+
return self.value / other.value # type: ignore
|
129
|
+
|
130
|
+
@overload
|
131
|
+
def __floordiv__(self: GenericValueType, other: int | float) -> GenericValueType:
|
132
|
+
...
|
133
|
+
|
134
|
+
@overload
|
135
|
+
def __floordiv__(
|
136
|
+
self: GenericValueType, other: GenericValueType
|
137
|
+
) -> InternalValueType:
|
138
|
+
...
|
139
|
+
|
140
|
+
def __floordiv__(
|
141
|
+
self: GenericValueType, other: GenericValueType | int | float
|
142
|
+
) -> GenericValueType | InternalValueType:
|
143
|
+
if not isinstance(other, (_GenericValue, int, float)):
|
144
|
+
raise TypeError("Cannot multiply different types")
|
145
|
+
if not isinstance(other, (int, float)) and type(self) is not type(other):
|
146
|
+
raise TypeError("Cannot multiply different types")
|
147
|
+
if other == 0:
|
148
|
+
raise ZeroDivisionError("Cannot divide by zero")
|
149
|
+
if isinstance(other, (int, float)):
|
150
|
+
return type(self)(self.value // other) # type: ignore
|
151
|
+
else:
|
152
|
+
return self.value // other.value # type: ignore
|
153
|
+
|
154
|
+
def __lt__(self: GenericValueType, other: GenericValueType | t.Literal[0]) -> bool:
|
155
|
+
if other == 0:
|
156
|
+
other = self.zero()
|
157
|
+
if not isinstance(other, _GenericValue):
|
158
|
+
raise TypeError("Cannot compare different types")
|
159
|
+
if type(self) is not type(other):
|
160
|
+
raise TypeError("Cannot compare different types")
|
161
|
+
return bool(self.value < other.value)
|
162
|
+
|
163
|
+
def __le__(self: GenericValueType, other: GenericValueType | t.Literal[0]) -> bool:
|
164
|
+
if other == 0:
|
165
|
+
other = self.zero()
|
166
|
+
if not isinstance(other, _GenericValue):
|
167
|
+
raise TypeError("Cannot compare different types")
|
168
|
+
if type(self) is not type(other):
|
169
|
+
raise TypeError("Cannot compare different types")
|
170
|
+
return bool(self.value <= other.value)
|
171
|
+
|
172
|
+
def __gt__(self: GenericValueType, other: GenericValueType | t.Literal[0]) -> bool:
|
173
|
+
if other == 0:
|
174
|
+
other = self.zero()
|
175
|
+
if not isinstance(other, _GenericValue):
|
176
|
+
raise TypeError("Cannot compare different types")
|
177
|
+
if type(self) is not type(other):
|
178
|
+
raise TypeError("Cannot compare different types")
|
179
|
+
return bool(self.value > other.value)
|
180
|
+
|
181
|
+
def __ge__(self: GenericValueType, other: GenericValueType | t.Literal[0]) -> bool:
|
182
|
+
if other == 0:
|
183
|
+
other = self.zero()
|
184
|
+
if not isinstance(other, _GenericValue):
|
185
|
+
raise TypeError("Cannot compare different types")
|
186
|
+
if type(self) is not type(other):
|
187
|
+
raise TypeError("Cannot compare different types")
|
188
|
+
return bool(self.value >= other.value)
|
189
|
+
|
190
|
+
def __eq__(self: GenericValueType, other: GenericValueType | t.Literal[0]) -> bool: # type: ignore
|
191
|
+
if other == 0:
|
192
|
+
other = self.zero()
|
193
|
+
if not isinstance(other, _GenericValue):
|
194
|
+
raise TypeError("Cannot compare different types")
|
195
|
+
if type(self) is not type(other):
|
196
|
+
raise TypeError("Cannot compare different types")
|
197
|
+
return bool(self.value == other.value)
|
198
|
+
|
199
|
+
def __ne__(self: GenericValueType, other: GenericValueType | t.Literal[0]) -> bool: # type: ignore
|
200
|
+
if other == 0:
|
201
|
+
other = self.zero()
|
202
|
+
if not isinstance(other, _GenericValue):
|
203
|
+
raise TypeError("Cannot compare different types")
|
204
|
+
if type(self) is not type(other):
|
205
|
+
raise TypeError("Cannot compare different types")
|
206
|
+
return bool(self.value != other.value)
|
207
|
+
|
208
|
+
def __repr__(self) -> str:
|
209
|
+
return f"{type(self).__name__}({self.value})"
|
210
|
+
|
211
|
+
def __float__(self) -> float:
|
212
|
+
return float(self.value)
|
213
|
+
|
214
|
+
def __radd__(
|
215
|
+
self: GenericValueType, other: GenericValueType | t.Literal[0] | int | float
|
216
|
+
) -> GenericValueType:
|
217
|
+
if isinstance(other, (_GenericValue, int, float)):
|
218
|
+
return self.__add__(other) # type: ignore[operator]
|
219
|
+
|
220
|
+
elif isinstance(other, (int, float)) and other == 0:
|
221
|
+
return self
|
222
|
+
|
223
|
+
else:
|
224
|
+
raise TypeError("Cannot add different types")
|
225
|
+
|
226
|
+
def __round__(self: GenericValueType, ndigits: int = 0) -> GenericValueType:
|
227
|
+
if not isinstance(self.value, (int, float)):
|
228
|
+
raise TypeError("Cannot round non-numeric types")
|
229
|
+
return type(self)(round(self.value, ndigits)) # type: ignore[arg-type]
|
230
|
+
|
231
|
+
def __bool__(self) -> bool:
|
232
|
+
return bool(self.value)
|
233
|
+
|
234
|
+
@classmethod
|
235
|
+
def __get_pydantic_core_schema__(
|
236
|
+
cls, source_type: t.Any, handler: GetCoreSchemaHandler
|
237
|
+
) -> CoreSchema:
|
238
|
+
# Support for Pydantic usage.
|
239
|
+
dt_schema = handler(str | int | float | dict)
|
240
|
+
return core_schema.no_info_after_validator_function(
|
241
|
+
lambda x: cls(x["value"] if isinstance(x, dict) else x),
|
242
|
+
dt_schema,
|
243
|
+
)
|
244
|
+
|
245
|
+
def with_fraction(self: GenericValueType, fraction: float) -> GenericValueType:
|
246
|
+
if not 0 <= fraction <= 1:
|
247
|
+
raise ValueError(f"Given fraction {fraction} is not in the range [0,1].")
|
248
|
+
return self.__class__(self.value * (1 + fraction)) # type: ignore[arg-type]
|
249
|
+
|
250
|
+
def without_fraction(self: GenericValueType, fraction: float) -> GenericValueType:
|
251
|
+
if not 0 <= fraction <= 1:
|
252
|
+
raise ValueError(f"Given fraction {fraction} is not in the range [0,1].")
|
253
|
+
return self.__class__(self.value * (1 - fraction)) # type: ignore[arg-type]
|
254
|
+
|
255
|
+
@classmethod
|
256
|
+
def zero(cls: type[GenericValueType]) -> GenericValueType:
|
257
|
+
return cls(0) # type: ignore[arg-type]
|
258
|
+
|
259
|
+
@property
|
260
|
+
def symbol(self) -> str:
|
261
|
+
return self.__class__.__name__
|
@@ -1,32 +1,35 @@
|
|
1
1
|
from pydantic import BaseModel
|
2
2
|
from tenacity import retry, stop_after_attempt, wait_fixed
|
3
3
|
from web3 import Web3
|
4
|
-
from web3.types import Wei
|
5
4
|
|
6
|
-
from prediction_market_agent_tooling.gtypes import
|
5
|
+
from prediction_market_agent_tooling.gtypes import (
|
6
|
+
ChecksumAddress,
|
7
|
+
CollateralToken,
|
8
|
+
xDai,
|
9
|
+
xDaiWei,
|
10
|
+
)
|
7
11
|
from prediction_market_agent_tooling.markets.omen.omen_contracts import (
|
8
12
|
WrappedxDaiContract,
|
9
13
|
sDaiContract,
|
10
14
|
)
|
11
|
-
from prediction_market_agent_tooling.tools.web3_utils import wei_to_xdai
|
12
15
|
|
13
16
|
|
14
17
|
class Balances(BaseModel):
|
15
18
|
xdai: xDai
|
16
|
-
wxdai:
|
17
|
-
sdai:
|
19
|
+
wxdai: CollateralToken
|
20
|
+
sdai: CollateralToken
|
18
21
|
|
19
22
|
@property
|
20
|
-
def total(self) ->
|
21
|
-
return
|
23
|
+
def total(self) -> CollateralToken:
|
24
|
+
return self.xdai.as_token + self.wxdai + self.sdai
|
22
25
|
|
23
26
|
|
24
27
|
@retry(stop=stop_after_attempt(3), wait=wait_fixed(1))
|
25
28
|
def get_balances(address: ChecksumAddress, web3: Web3 | None = None) -> Balances:
|
26
29
|
if not web3:
|
27
30
|
web3 = WrappedxDaiContract().get_web3()
|
28
|
-
xdai_balance =
|
29
|
-
xdai =
|
30
|
-
wxdai =
|
31
|
-
sdai =
|
31
|
+
xdai_balance = xDaiWei(web3.eth.get_balance(address))
|
32
|
+
xdai = xdai_balance.as_xdai
|
33
|
+
wxdai = WrappedxDaiContract().balanceOf(address, web3=web3).as_token
|
34
|
+
sdai = sDaiContract().balanceOf(address, web3=web3).as_token
|
32
35
|
return Balances(xdai=xdai, wxdai=wxdai, sdai=sdai)
|
@@ -1,3 +1,4 @@
|
|
1
|
+
from prediction_market_agent_tooling.gtypes import CollateralToken, OutcomeToken
|
1
2
|
from prediction_market_agent_tooling.markets.market_fees import MarketFees
|
2
3
|
from prediction_market_agent_tooling.tools.betting_strategies.utils import SimpleBet
|
3
4
|
|
@@ -8,7 +9,7 @@ def check_is_valid_probability(probability: float) -> None:
|
|
8
9
|
|
9
10
|
|
10
11
|
def get_kelly_bet_simplified(
|
11
|
-
max_bet:
|
12
|
+
max_bet: CollateralToken,
|
12
13
|
market_p_yes: float,
|
13
14
|
estimated_p_yes: float,
|
14
15
|
confidence: float,
|
@@ -51,17 +52,17 @@ def get_kelly_bet_simplified(
|
|
51
52
|
kelly_fraction = edge / odds
|
52
53
|
|
53
54
|
# Ensure bet size is non-negative does not exceed the wallet balance
|
54
|
-
bet_size = min(kelly_fraction * max_bet, max_bet)
|
55
|
+
bet_size = CollateralToken(min(kelly_fraction * max_bet.value, max_bet.value))
|
55
56
|
|
56
57
|
return SimpleBet(direction=bet_direction, size=bet_size)
|
57
58
|
|
58
59
|
|
59
60
|
def get_kelly_bet_full(
|
60
|
-
yes_outcome_pool_size:
|
61
|
-
no_outcome_pool_size:
|
61
|
+
yes_outcome_pool_size: OutcomeToken,
|
62
|
+
no_outcome_pool_size: OutcomeToken,
|
62
63
|
estimated_p_yes: float,
|
63
64
|
confidence: float,
|
64
|
-
max_bet:
|
65
|
+
max_bet: CollateralToken,
|
65
66
|
fees: MarketFees,
|
66
67
|
) -> SimpleBet:
|
67
68
|
"""
|
@@ -97,13 +98,13 @@ def get_kelly_bet_full(
|
|
97
98
|
check_is_valid_probability(confidence)
|
98
99
|
|
99
100
|
if max_bet == 0:
|
100
|
-
return SimpleBet(direction=True, size=0)
|
101
|
+
return SimpleBet(direction=True, size=CollateralToken(0))
|
101
102
|
|
102
|
-
x = yes_outcome_pool_size
|
103
|
-
y = no_outcome_pool_size
|
103
|
+
x = yes_outcome_pool_size.value
|
104
|
+
y = no_outcome_pool_size.value
|
104
105
|
p = estimated_p_yes
|
105
106
|
c = confidence
|
106
|
-
b = max_bet
|
107
|
+
b = max_bet.value
|
107
108
|
f = 1 - fee
|
108
109
|
|
109
110
|
if x == y:
|
@@ -144,5 +145,6 @@ def get_kelly_bet_full(
|
|
144
145
|
|
145
146
|
# Clip the bet size to max_bet to account for rounding errors.
|
146
147
|
return SimpleBet(
|
147
|
-
direction=kelly_bet_amount > 0,
|
148
|
+
direction=kelly_bet_amount > 0,
|
149
|
+
size=CollateralToken(min(max_bet.value, abs(kelly_bet_amount))),
|
148
150
|
)
|
@@ -2,19 +2,22 @@ from functools import reduce
|
|
2
2
|
|
3
3
|
import numpy as np
|
4
4
|
|
5
|
-
from prediction_market_agent_tooling.gtypes import
|
5
|
+
from prediction_market_agent_tooling.gtypes import (
|
6
|
+
CollateralToken,
|
7
|
+
OutcomeToken,
|
8
|
+
Probability,
|
9
|
+
)
|
6
10
|
from prediction_market_agent_tooling.markets.omen.omen import (
|
7
11
|
MarketFees,
|
8
12
|
OmenAgentMarket,
|
9
13
|
)
|
10
14
|
from prediction_market_agent_tooling.tools.betting_strategies.utils import SimpleBet
|
11
15
|
from prediction_market_agent_tooling.tools.utils import check_not_none
|
12
|
-
from prediction_market_agent_tooling.tools.web3_utils import wei_to_xdai, xdai_to_wei
|
13
16
|
|
14
17
|
|
15
18
|
def get_market_moving_bet(
|
16
|
-
yes_outcome_pool_size:
|
17
|
-
no_outcome_pool_size:
|
19
|
+
yes_outcome_pool_size: OutcomeToken,
|
20
|
+
no_outcome_pool_size: OutcomeToken,
|
18
21
|
market_p_yes: float,
|
19
22
|
target_p_yes: float,
|
20
23
|
fees: MarketFees,
|
@@ -42,20 +45,21 @@ def get_market_moving_bet(
|
|
42
45
|
fixed_product = yes_outcome_pool_size * no_outcome_pool_size
|
43
46
|
bet_direction: bool = target_p_yes > market_p_yes
|
44
47
|
|
45
|
-
min_bet_amount = 0.0
|
46
|
-
max_bet_amount =
|
48
|
+
min_bet_amount = CollateralToken(0.0)
|
49
|
+
max_bet_amount = (
|
47
50
|
yes_outcome_pool_size + no_outcome_pool_size
|
48
|
-
) # TODO set a better upper bound
|
51
|
+
).as_token * 100 # TODO set a better upper bound
|
49
52
|
|
50
53
|
# Binary search for the optimal bet amount
|
51
54
|
for _ in range(max_iters):
|
52
55
|
bet_amount = (min_bet_amount + max_bet_amount) / 2
|
53
|
-
amounts_diff = fees.
|
56
|
+
amounts_diff = fees.get_after_fees(bet_amount)
|
57
|
+
amounts_diff_as_ot = OutcomeToken.from_token(amounts_diff)
|
54
58
|
|
55
59
|
# Initial new amounts are old amounts + equal new amounts for each outcome
|
56
|
-
yes_outcome_new_pool_size = yes_outcome_pool_size +
|
57
|
-
no_outcome_new_pool_size = no_outcome_pool_size +
|
58
|
-
new_amounts = {
|
60
|
+
yes_outcome_new_pool_size = yes_outcome_pool_size + amounts_diff_as_ot
|
61
|
+
no_outcome_new_pool_size = no_outcome_pool_size + amounts_diff_as_ot
|
62
|
+
new_amounts: dict[bool, OutcomeToken] = {
|
59
63
|
True: yes_outcome_new_pool_size,
|
60
64
|
False: no_outcome_new_pool_size,
|
61
65
|
}
|
@@ -63,15 +67,20 @@ def get_market_moving_bet(
|
|
63
67
|
# Now give away tokens at `bet_outcome_index` to restore invariant
|
64
68
|
new_product = yes_outcome_new_pool_size * no_outcome_new_pool_size
|
65
69
|
dx = (new_product - fixed_product) / new_amounts[not bet_direction]
|
66
|
-
new_amounts[bet_direction] -= dx
|
70
|
+
new_amounts[bet_direction] -= OutcomeToken(dx)
|
67
71
|
|
68
72
|
# Check that the invariant is restored
|
69
73
|
assert np.isclose(
|
70
|
-
reduce(lambda x, y: x * y, list(new_amounts.values()), 1.0),
|
74
|
+
reduce(lambda x, y: x * y.value, list(new_amounts.values()), 1.0),
|
71
75
|
float(fixed_product),
|
72
76
|
)
|
73
77
|
|
74
|
-
new_p_yes = Probability(
|
78
|
+
new_p_yes = Probability(
|
79
|
+
(
|
80
|
+
new_amounts[False]
|
81
|
+
/ sum(list(new_amounts.values()), start=OutcomeToken(0))
|
82
|
+
)
|
83
|
+
)
|
75
84
|
if abs(target_p_yes - new_p_yes) < 1e-6:
|
76
85
|
break
|
77
86
|
elif new_p_yes > target_p_yes:
|
@@ -97,33 +106,31 @@ def _sanity_check_omen_market_moving_bet(
|
|
97
106
|
using the adjusted outcome pool sizes to calculate the new p_yes.
|
98
107
|
"""
|
99
108
|
buy_amount_ = market.get_contract().calcBuyAmount(
|
100
|
-
investment_amount=
|
109
|
+
investment_amount=bet_to_check.size.as_wei,
|
101
110
|
outcome_index=market.get_outcome_index(
|
102
111
|
market.get_outcome_str_from_bool(bet_to_check.direction)
|
103
112
|
),
|
104
113
|
)
|
105
|
-
buy_amount =
|
114
|
+
buy_amount = buy_amount_.as_outcome_token
|
106
115
|
|
107
116
|
outcome_token_pool = check_not_none(market.outcome_token_pool)
|
108
117
|
yes_outcome_pool_size = outcome_token_pool[market.get_outcome_str_from_bool(True)]
|
109
118
|
no_outcome_pool_size = outcome_token_pool[market.get_outcome_str_from_bool(False)]
|
110
|
-
market_const = yes_outcome_pool_size * no_outcome_pool_size
|
119
|
+
market_const = yes_outcome_pool_size.value * no_outcome_pool_size.value
|
111
120
|
|
112
|
-
bet_to_check_size_after_fees = market.fees.
|
113
|
-
bet_to_check.size
|
114
|
-
)
|
121
|
+
bet_to_check_size_after_fees = market.fees.get_after_fees(bet_to_check.size).value
|
115
122
|
|
116
123
|
# When you buy 'yes' tokens, you add your bet size to the both pools, then
|
117
124
|
# subtract `buy_amount` from the 'yes' pool. And vice versa for 'no' tokens.
|
118
125
|
new_yes_outcome_pool_size = (
|
119
|
-
yes_outcome_pool_size
|
126
|
+
yes_outcome_pool_size.value
|
120
127
|
+ bet_to_check_size_after_fees
|
121
|
-
- float(bet_to_check.direction) * buy_amount
|
128
|
+
- float(bet_to_check.direction) * buy_amount.value
|
122
129
|
)
|
123
130
|
new_no_outcome_pool_size = (
|
124
|
-
no_outcome_pool_size
|
131
|
+
no_outcome_pool_size.value
|
125
132
|
+ bet_to_check_size_after_fees
|
126
|
-
- float(not bet_to_check.direction) * buy_amount
|
133
|
+
- float(not bet_to_check.direction) * buy_amount.value
|
127
134
|
)
|
128
135
|
new_market_const = new_yes_outcome_pool_size * new_no_outcome_pool_size
|
129
136
|
# Check the invariant is restored
|
@@ -14,6 +14,7 @@ from prediction_market_agent_tooling.gtypes import (
|
|
14
14
|
ABI,
|
15
15
|
ChainID,
|
16
16
|
ChecksumAddress,
|
17
|
+
CollateralToken,
|
17
18
|
Nonce,
|
18
19
|
TxParams,
|
19
20
|
TxReceipt,
|
@@ -153,7 +154,7 @@ class ContractBaseClass(BaseModel):
|
|
153
154
|
api_keys=api_keys,
|
154
155
|
function_name=function_name,
|
155
156
|
function_params=function_params,
|
156
|
-
tx_params={"value": amount_wei, **(tx_params or {})},
|
157
|
+
tx_params={"value": amount_wei.value, **(tx_params or {})},
|
157
158
|
timeout=timeout,
|
158
159
|
web3=web3,
|
159
160
|
)
|
@@ -243,12 +244,13 @@ class ContractERC20BaseClass(ContractBaseClass):
|
|
243
244
|
)
|
244
245
|
|
245
246
|
def balanceOf(self, for_address: ChecksumAddress, web3: Web3 | None = None) -> Wei:
|
246
|
-
balance
|
247
|
+
balance = Wei(self.call("balanceOf", [for_address], web3=web3))
|
247
248
|
return balance
|
248
249
|
|
249
|
-
def
|
250
|
-
|
251
|
-
|
250
|
+
def balance_of_in_tokens(
|
251
|
+
self, for_address: ChecksumAddress, web3: Web3 | None = None
|
252
|
+
) -> CollateralToken:
|
253
|
+
return self.balanceOf(for_address, web3=web3).as_token
|
252
254
|
|
253
255
|
|
254
256
|
class ContractDepositableWrapperERC20BaseClass(ContractERC20BaseClass):
|
@@ -355,11 +357,11 @@ class ContractERC4626BaseClass(ContractERC20BaseClass):
|
|
355
357
|
return self.withdraw(api_keys=api_keys, assets_wei=assets, web3=web3)
|
356
358
|
|
357
359
|
def convertToShares(self, assets: Wei, web3: Web3 | None = None) -> Wei:
|
358
|
-
shares
|
360
|
+
shares = Wei(self.call("convertToShares", [assets], web3=web3))
|
359
361
|
return shares
|
360
362
|
|
361
363
|
def convertToAssets(self, shares: Wei, web3: Web3 | None = None) -> Wei:
|
362
|
-
assets
|
364
|
+
assets = Wei(self.call("convertToAssets", [shares], web3=web3))
|
363
365
|
return assets
|
364
366
|
|
365
367
|
def get_asset_token_contract(
|
@@ -434,8 +436,8 @@ class ContractERC721BaseClass(ContractBaseClass):
|
|
434
436
|
web3=web3,
|
435
437
|
)
|
436
438
|
|
437
|
-
def balanceOf(self, owner: ChecksumAddress, web3: Web3 | None = None) ->
|
438
|
-
balance
|
439
|
+
def balanceOf(self, owner: ChecksumAddress, web3: Web3 | None = None) -> Wei:
|
440
|
+
balance = Wei(self.call("balanceOf", [owner], web3=web3))
|
439
441
|
return balance
|
440
442
|
|
441
443
|
def owner_of(self, token_id: int, web3: Web3 | None = None) -> ChecksumAddress:
|
@@ -604,12 +606,14 @@ def contract_implements_function(
|
|
604
606
|
|
605
607
|
|
606
608
|
def init_collateral_token_contract(
|
607
|
-
address: ChecksumAddress, web3: Web3
|
609
|
+
address: ChecksumAddress, web3: Web3 | None
|
608
610
|
) -> ContractERC20BaseClass:
|
609
611
|
"""
|
610
612
|
Checks if the given contract is Depositable ERC-20, ERC-20 or ERC-4626 and returns the appropriate class instance.
|
611
613
|
Throws an error if the contract is neither of them.
|
612
614
|
"""
|
615
|
+
web3 = web3 or RPCConfig().get_web3()
|
616
|
+
|
613
617
|
if contract_implements_function(address, "asset", web3=web3):
|
614
618
|
return ContractERC4626BaseClass(address=address)
|
615
619
|
|
@@ -19,10 +19,9 @@ from web3 import Web3
|
|
19
19
|
from web3.constants import ADDRESS_ZERO
|
20
20
|
|
21
21
|
from prediction_market_agent_tooling.config import APIKeys
|
22
|
-
from prediction_market_agent_tooling.gtypes import ChecksumAddress,
|
22
|
+
from prediction_market_agent_tooling.gtypes import ChecksumAddress, CollateralToken, Wei
|
23
23
|
from prediction_market_agent_tooling.loggers import logger
|
24
24
|
from prediction_market_agent_tooling.tools.cow.cow_order import swap_tokens_waiting
|
25
|
-
from prediction_market_agent_tooling.tools.web3_utils import xdai_to_wei
|
26
25
|
|
27
26
|
COW_ENV: Envs = "prod"
|
28
27
|
|
@@ -94,14 +93,14 @@ class CowManager:
|
|
94
93
|
|
95
94
|
@staticmethod
|
96
95
|
def swap(
|
97
|
-
amount:
|
96
|
+
amount: CollateralToken,
|
98
97
|
sell_token: ChecksumAddress,
|
99
98
|
buy_token: ChecksumAddress,
|
100
99
|
api_keys: APIKeys,
|
101
100
|
web3: Web3 | None = None,
|
102
101
|
) -> OrderMetaData:
|
103
102
|
order_metadata = swap_tokens_waiting(
|
104
|
-
amount_wei=
|
103
|
+
amount_wei=amount.as_wei,
|
105
104
|
sell_token=sell_token,
|
106
105
|
buy_token=buy_token,
|
107
106
|
api_keys=api_keys,
|