prediction-market-agent-tooling 0.61.1.dev463__py3-none-any.whl → 0.61.1.dev482__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 +5 -4
- prediction_market_agent_tooling/deploy/betting_strategy.py +69 -53
- prediction_market_agent_tooling/gtypes.py +27 -105
- prediction_market_agent_tooling/jobs/jobs_models.py +7 -5
- prediction_market_agent_tooling/jobs/omen/omen_jobs.py +17 -13
- prediction_market_agent_tooling/markets/agent_market.py +52 -96
- prediction_market_agent_tooling/markets/blockchain_utils.py +27 -1
- prediction_market_agent_tooling/markets/data_models.py +44 -40
- prediction_market_agent_tooling/markets/manifold/api.py +6 -2
- prediction_market_agent_tooling/markets/manifold/data_models.py +25 -33
- prediction_market_agent_tooling/markets/manifold/manifold.py +11 -8
- prediction_market_agent_tooling/markets/market_fees.py +2 -4
- prediction_market_agent_tooling/markets/omen/data_models.py +57 -66
- prediction_market_agent_tooling/markets/omen/omen.py +249 -214
- prediction_market_agent_tooling/markets/omen/omen_contracts.py +29 -31
- prediction_market_agent_tooling/markets/omen/omen_resolving.py +14 -7
- prediction_market_agent_tooling/markets/omen/omen_subgraph_handler.py +14 -20
- 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 +5 -3
- prediction_market_agent_tooling/markets/seer/data_models.py +12 -8
- prediction_market_agent_tooling/markets/seer/seer.py +71 -85
- prediction_market_agent_tooling/markets/seer/seer_contracts.py +5 -10
- prediction_market_agent_tooling/markets/seer/seer_subgraph_handler.py +14 -9
- prediction_market_agent_tooling/monitor/monitor.py +2 -2
- prediction_market_agent_tooling/tools/balances.py +11 -9
- prediction_market_agent_tooling/tools/betting_strategies/kelly_criterion.py +10 -12
- prediction_market_agent_tooling/tools/betting_strategies/market_moving.py +24 -27
- prediction_market_agent_tooling/tools/betting_strategies/utils.py +1 -3
- prediction_market_agent_tooling/tools/contract.py +10 -14
- prediction_market_agent_tooling/tools/cow/cow_manager.py +4 -3
- prediction_market_agent_tooling/tools/cow/cow_order.py +4 -3
- prediction_market_agent_tooling/tools/langfuse_client_utils.py +1 -13
- prediction_market_agent_tooling/tools/omen/sell_positions.py +3 -6
- prediction_market_agent_tooling/tools/safe.py +6 -5
- prediction_market_agent_tooling/tools/tokens/auto_deposit.py +30 -32
- prediction_market_agent_tooling/tools/tokens/auto_withdraw.py +22 -5
- prediction_market_agent_tooling/tools/tokens/main_token.py +2 -2
- prediction_market_agent_tooling/tools/utils.py +8 -14
- prediction_market_agent_tooling/tools/web3_utils.py +41 -24
- {prediction_market_agent_tooling-0.61.1.dev463.dist-info → prediction_market_agent_tooling-0.61.1.dev482.dist-info}/METADATA +1 -2
- {prediction_market_agent_tooling-0.61.1.dev463.dist-info → prediction_market_agent_tooling-0.61.1.dev482.dist-info}/RECORD +45 -48
- prediction_market_agent_tooling/tools/_generic_value.py +0 -255
- prediction_market_agent_tooling/tools/tokens/token_utils.py +0 -46
- prediction_market_agent_tooling/tools/tokens/usd.py +0 -63
- {prediction_market_agent_tooling-0.61.1.dev463.dist-info → prediction_market_agent_tooling-0.61.1.dev482.dist-info}/LICENSE +0 -0
- {prediction_market_agent_tooling-0.61.1.dev463.dist-info → prediction_market_agent_tooling-0.61.1.dev482.dist-info}/WHEEL +0 -0
- {prediction_market_agent_tooling-0.61.1.dev463.dist-info → prediction_market_agent_tooling-0.61.1.dev482.dist-info}/entry_points.txt +0 -0
@@ -6,22 +6,17 @@ from pydantic import BaseModel, field_validator, model_validator
|
|
6
6
|
from pydantic_core.core_schema import FieldValidationInfo
|
7
7
|
|
8
8
|
from prediction_market_agent_tooling.config import APIKeys
|
9
|
-
from prediction_market_agent_tooling.gtypes import
|
10
|
-
OutcomeStr,
|
11
|
-
OutcomeToken,
|
12
|
-
Probability,
|
13
|
-
Token,
|
14
|
-
)
|
9
|
+
from prediction_market_agent_tooling.gtypes import OutcomeStr, Probability
|
15
10
|
from prediction_market_agent_tooling.markets.data_models import (
|
16
|
-
USD,
|
17
11
|
Bet,
|
18
|
-
|
12
|
+
BetAmount,
|
13
|
+
Currency,
|
19
14
|
PlacedTrade,
|
20
15
|
Position,
|
21
16
|
ProbabilisticAnswer,
|
22
17
|
Resolution,
|
23
18
|
ResolvedBet,
|
24
|
-
|
19
|
+
TokenAmount,
|
25
20
|
)
|
26
21
|
from prediction_market_agent_tooling.markets.market_fees import MarketFees
|
27
22
|
from prediction_market_agent_tooling.tools.utils import (
|
@@ -60,28 +55,31 @@ class AgentMarket(BaseModel):
|
|
60
55
|
Contains everything that is needed for an agent to make a prediction.
|
61
56
|
"""
|
62
57
|
|
58
|
+
currency: t.ClassVar[Currency]
|
63
59
|
base_url: t.ClassVar[str]
|
64
60
|
|
65
61
|
id: str
|
66
62
|
question: str
|
67
63
|
description: str | None
|
68
|
-
outcomes:
|
69
|
-
outcome_token_pool:
|
64
|
+
outcomes: list[str]
|
65
|
+
outcome_token_pool: (
|
66
|
+
dict[str, float] | None
|
67
|
+
) # Should be in currency of `currency` above.
|
70
68
|
resolution: Resolution | None
|
71
69
|
created_time: DatetimeUTC | None
|
72
70
|
close_time: DatetimeUTC | None
|
73
71
|
current_p_yes: Probability
|
74
72
|
url: str
|
75
|
-
volume:
|
73
|
+
volume: float | None # Should be in currency of `currency` above.
|
76
74
|
fees: MarketFees
|
77
75
|
|
78
76
|
@field_validator("outcome_token_pool")
|
79
77
|
def validate_outcome_token_pool(
|
80
78
|
cls,
|
81
|
-
outcome_token_pool: dict[str,
|
79
|
+
outcome_token_pool: dict[str, float] | None,
|
82
80
|
info: FieldValidationInfo,
|
83
|
-
) -> dict[str,
|
84
|
-
outcomes:
|
81
|
+
) -> dict[str, float] | None:
|
82
|
+
outcomes: list[str] = check_not_none(info.data.get("outcomes"))
|
85
83
|
if outcome_token_pool is not None:
|
86
84
|
outcome_keys = set(outcome_token_pool.keys())
|
87
85
|
expected_keys = set(outcomes)
|
@@ -104,28 +102,20 @@ class AgentMarket(BaseModel):
|
|
104
102
|
return Probability(1 - self.current_p_yes)
|
105
103
|
|
106
104
|
@property
|
107
|
-
def yes_outcome_price(self) ->
|
105
|
+
def yes_outcome_price(self) -> float:
|
108
106
|
"""
|
109
107
|
Price at prediction market is equal to the probability of given outcome.
|
110
108
|
Keep as an extra property, in case it wouldn't be true for some prediction market platform.
|
111
109
|
"""
|
112
|
-
return
|
113
|
-
|
114
|
-
@property
|
115
|
-
def yes_outcome_price_usd(self) -> USD:
|
116
|
-
return self.get_token_in_usd(self.yes_outcome_price)
|
110
|
+
return self.current_p_yes
|
117
111
|
|
118
112
|
@property
|
119
|
-
def no_outcome_price(self) ->
|
113
|
+
def no_outcome_price(self) -> float:
|
120
114
|
"""
|
121
115
|
Price at prediction market is equal to the probability of given outcome.
|
122
116
|
Keep as an extra property, in case it wouldn't be true for some prediction market platform.
|
123
117
|
"""
|
124
|
-
return
|
125
|
-
|
126
|
-
@property
|
127
|
-
def no_outcome_price_usd(self) -> USD:
|
128
|
-
return self.get_token_in_usd(self.no_outcome_price)
|
118
|
+
return self.current_p_no
|
129
119
|
|
130
120
|
@property
|
131
121
|
def probable_resolution(self) -> Resolution:
|
@@ -160,86 +150,48 @@ class AgentMarket(BaseModel):
|
|
160
150
|
"""
|
161
151
|
raise NotImplementedError("Subclasses must implement this method")
|
162
152
|
|
163
|
-
def get_last_trade_yes_outcome_price(self) ->
|
153
|
+
def get_last_trade_yes_outcome_price(self) -> float | None:
|
164
154
|
# Price on prediction markets are, by definition, equal to the probability of an outcome.
|
165
155
|
# Just making it explicit in this function.
|
166
156
|
if last_trade_p_yes := self.get_last_trade_p_yes():
|
167
|
-
return
|
157
|
+
return float(last_trade_p_yes)
|
168
158
|
return None
|
169
159
|
|
170
|
-
def
|
171
|
-
if last_trade_yes_outcome_price := self.get_last_trade_yes_outcome_price():
|
172
|
-
return self.get_token_in_usd(last_trade_yes_outcome_price)
|
173
|
-
return None
|
174
|
-
|
175
|
-
def get_last_trade_no_outcome_price(self) -> Token | None:
|
160
|
+
def get_last_trade_no_outcome_price(self) -> float | None:
|
176
161
|
# Price on prediction markets are, by definition, equal to the probability of an outcome.
|
177
162
|
# Just making it explicit in this function.
|
178
163
|
if last_trade_p_no := self.get_last_trade_p_no():
|
179
|
-
return
|
180
|
-
return None
|
181
|
-
|
182
|
-
def get_last_trade_no_outcome_price_usd(self) -> USD | None:
|
183
|
-
if last_trade_no_outcome_price := self.get_last_trade_no_outcome_price():
|
184
|
-
return self.get_token_in_usd(last_trade_no_outcome_price)
|
164
|
+
return float(last_trade_p_no)
|
185
165
|
return None
|
186
166
|
|
187
|
-
def
|
188
|
-
|
189
|
-
return OutcomeToken.from_token(tiny_amount / 10)
|
190
|
-
|
191
|
-
def get_token_in_usd(self, x: Token) -> USD:
|
192
|
-
"""
|
193
|
-
Token of this market can have whatever worth (e.g. sDai and ETH markets will have different worth of 1 token). Use this to convert it to USD.
|
194
|
-
"""
|
195
|
-
raise NotImplementedError("Subclasses must implement this method")
|
196
|
-
|
197
|
-
def get_usd_in_token(self, x: USD) -> Token:
|
198
|
-
"""
|
199
|
-
Markets on a single platform can have different tokens as collateral (sDai, wxDai, GNO, ...). Use this to convert USD to the token of this market.
|
200
|
-
"""
|
201
|
-
raise NotImplementedError("Subclasses must implement this method")
|
202
|
-
|
203
|
-
def get_sell_value_of_outcome_token(
|
204
|
-
self, outcome: str, amount: OutcomeToken
|
205
|
-
) -> Token:
|
206
|
-
"""
|
207
|
-
When you hold OutcomeToken(s), it's easy to calculate how much you get at the end if you win (1 OutcomeToken will equal to 1 Token).
|
208
|
-
But use this to figure out, how much are these outcome tokens worth right now (for how much you can sell them).
|
209
|
-
"""
|
210
|
-
raise NotImplementedError("Subclasses must implement this method")
|
211
|
-
|
212
|
-
def get_in_usd(self, x: USD | Token) -> USD:
|
213
|
-
if isinstance(x, USD):
|
214
|
-
return x
|
215
|
-
return self.get_token_in_usd(x)
|
167
|
+
def get_bet_amount(self, amount: float) -> BetAmount:
|
168
|
+
return BetAmount(amount=amount, currency=self.currency)
|
216
169
|
|
217
|
-
|
218
|
-
|
219
|
-
|
220
|
-
|
170
|
+
@classmethod
|
171
|
+
def get_liquidatable_amount(cls) -> BetAmount:
|
172
|
+
tiny_amount = cls.get_tiny_bet_amount()
|
173
|
+
tiny_amount.amount /= 10
|
174
|
+
return tiny_amount
|
221
175
|
|
222
|
-
|
223
|
-
|
224
|
-
Tiny bet amount that the platform still allows us to do.
|
225
|
-
"""
|
176
|
+
@classmethod
|
177
|
+
def get_tiny_bet_amount(cls) -> BetAmount:
|
226
178
|
raise NotImplementedError("Subclasses must implement this method")
|
227
179
|
|
228
180
|
def liquidate_existing_positions(self, outcome: bool) -> None:
|
229
181
|
raise NotImplementedError("Subclasses must implement this method")
|
230
182
|
|
231
|
-
def place_bet(self, outcome: bool, amount:
|
183
|
+
def place_bet(self, outcome: bool, amount: BetAmount) -> str:
|
232
184
|
raise NotImplementedError("Subclasses must implement this method")
|
233
185
|
|
234
|
-
def buy_tokens(self, outcome: bool, amount:
|
186
|
+
def buy_tokens(self, outcome: bool, amount: TokenAmount) -> str:
|
235
187
|
return self.place_bet(outcome=outcome, amount=amount)
|
236
188
|
|
237
189
|
def get_buy_token_amount(
|
238
|
-
self, bet_amount:
|
239
|
-
) ->
|
190
|
+
self, bet_amount: BetAmount, direction: bool
|
191
|
+
) -> TokenAmount:
|
240
192
|
raise NotImplementedError("Subclasses must implement this method")
|
241
193
|
|
242
|
-
def sell_tokens(self, outcome: bool, amount:
|
194
|
+
def sell_tokens(self, outcome: bool, amount: TokenAmount) -> str:
|
243
195
|
raise NotImplementedError("Subclasses must implement this method")
|
244
196
|
|
245
197
|
@staticmethod
|
@@ -263,8 +215,8 @@ class AgentMarket(BaseModel):
|
|
263
215
|
"""
|
264
216
|
raise NotImplementedError("Subclasses must implement this method")
|
265
217
|
|
266
|
-
@
|
267
|
-
def get_trade_balance(
|
218
|
+
@staticmethod
|
219
|
+
def get_trade_balance(api_keys: APIKeys) -> float:
|
268
220
|
"""
|
269
221
|
Return balance that can be used to trade on the given market.
|
270
222
|
"""
|
@@ -320,11 +272,11 @@ class AgentMarket(BaseModel):
|
|
320
272
|
def is_resolved(self) -> bool:
|
321
273
|
return self.resolution is not None
|
322
274
|
|
323
|
-
def get_liquidity(self) ->
|
275
|
+
def get_liquidity(self) -> TokenAmount:
|
324
276
|
raise NotImplementedError("Subclasses must implement this method")
|
325
277
|
|
326
278
|
def has_liquidity(self) -> bool:
|
327
|
-
return self.get_liquidity() > 0
|
279
|
+
return self.get_liquidity().amount > 0
|
328
280
|
|
329
281
|
def has_successful_resolution(self) -> bool:
|
330
282
|
return self.resolution in [Resolution.YES, Resolution.NO]
|
@@ -335,7 +287,7 @@ class AgentMarket(BaseModel):
|
|
335
287
|
def get_outcome_str_from_bool(self, outcome: bool) -> OutcomeStr:
|
336
288
|
raise NotImplementedError("Subclasses must implement this method")
|
337
289
|
|
338
|
-
def get_outcome_str(self, outcome_index: int) ->
|
290
|
+
def get_outcome_str(self, outcome_index: int) -> str:
|
339
291
|
try:
|
340
292
|
return self.outcomes[outcome_index]
|
341
293
|
except IndexError:
|
@@ -349,19 +301,16 @@ class AgentMarket(BaseModel):
|
|
349
301
|
except ValueError:
|
350
302
|
raise ValueError(f"Outcome `{outcome}` not found in `{self.outcomes}`.")
|
351
303
|
|
352
|
-
def get_token_balance(self, user_id: str, outcome: str) ->
|
304
|
+
def get_token_balance(self, user_id: str, outcome: str) -> TokenAmount:
|
353
305
|
raise NotImplementedError("Subclasses must implement this method")
|
354
306
|
|
355
|
-
def get_position(self, user_id: str) ->
|
307
|
+
def get_position(self, user_id: str) -> Position | None:
|
356
308
|
raise NotImplementedError("Subclasses must implement this method")
|
357
309
|
|
358
310
|
@classmethod
|
359
311
|
def get_positions(
|
360
|
-
cls,
|
361
|
-
|
362
|
-
liquid_only: bool = False,
|
363
|
-
larger_than: OutcomeToken = OutcomeToken(0),
|
364
|
-
) -> t.Sequence[Position]:
|
312
|
+
cls, user_id: str, liquid_only: bool = False, larger_than: float = 0
|
313
|
+
) -> list[Position]:
|
365
314
|
"""
|
366
315
|
Get all non-zero positions a user has in any market.
|
367
316
|
|
@@ -372,6 +321,13 @@ class AgentMarket(BaseModel):
|
|
372
321
|
"""
|
373
322
|
raise NotImplementedError("Subclasses must implement this method")
|
374
323
|
|
324
|
+
@classmethod
|
325
|
+
def get_positions_value(cls, positions: list[Position]) -> BetAmount:
|
326
|
+
"""
|
327
|
+
Get the total value of all positions held by a user.
|
328
|
+
"""
|
329
|
+
raise NotImplementedError("Subclasses must implement this method")
|
330
|
+
|
375
331
|
def can_be_traded(self) -> bool:
|
376
332
|
if self.is_closed() or not self.has_liquidity():
|
377
333
|
return False
|
@@ -384,7 +340,7 @@ class AgentMarket(BaseModel):
|
|
384
340
|
def has_token_pool(self) -> bool:
|
385
341
|
return self.outcome_token_pool is not None
|
386
342
|
|
387
|
-
def get_pool_tokens(self, outcome: str) ->
|
343
|
+
def get_pool_tokens(self, outcome: str) -> float:
|
388
344
|
if not self.outcome_token_pool:
|
389
345
|
raise ValueError("Outcome token pool is not available.")
|
390
346
|
|
@@ -2,7 +2,13 @@ from web3 import Web3
|
|
2
2
|
from web3.constants import HASH_ZERO
|
3
3
|
|
4
4
|
from prediction_market_agent_tooling.config import APIKeys
|
5
|
-
from prediction_market_agent_tooling.gtypes import
|
5
|
+
from prediction_market_agent_tooling.gtypes import (
|
6
|
+
ChecksumAddress,
|
7
|
+
HexBytes,
|
8
|
+
HexStr,
|
9
|
+
xDai,
|
10
|
+
xdai_type,
|
11
|
+
)
|
6
12
|
from prediction_market_agent_tooling.loggers import logger
|
7
13
|
from prediction_market_agent_tooling.markets.agent_market import ProcessedTradedMarket
|
8
14
|
from prediction_market_agent_tooling.markets.omen.data_models import (
|
@@ -12,11 +18,31 @@ from prediction_market_agent_tooling.markets.omen.data_models import (
|
|
12
18
|
from prediction_market_agent_tooling.markets.omen.omen_contracts import (
|
13
19
|
OmenAgentResultMappingContract,
|
14
20
|
)
|
21
|
+
from prediction_market_agent_tooling.tools.balances import get_balances
|
15
22
|
from prediction_market_agent_tooling.tools.ipfs.ipfs_handler import IPFSHandler
|
16
23
|
from prediction_market_agent_tooling.tools.utils import BPS_CONSTANT
|
17
24
|
from prediction_market_agent_tooling.tools.web3_utils import ipfscidv0_to_byte32
|
18
25
|
|
19
26
|
|
27
|
+
def get_total_balance(
|
28
|
+
address: ChecksumAddress,
|
29
|
+
web3: Web3 | None = None,
|
30
|
+
sum_xdai: bool = True,
|
31
|
+
sum_wxdai: bool = True,
|
32
|
+
) -> xDai:
|
33
|
+
"""
|
34
|
+
Checks if the total balance of xDai and wxDai in the wallet is above the minimum required balance.
|
35
|
+
"""
|
36
|
+
current_balances = get_balances(address, web3)
|
37
|
+
# xDai and wxDai have equal value and can be exchanged for almost no cost, so we can sum them up.
|
38
|
+
total_balance = 0.0
|
39
|
+
if sum_xdai:
|
40
|
+
total_balance += current_balances.xdai
|
41
|
+
if sum_wxdai:
|
42
|
+
total_balance += current_balances.wxdai
|
43
|
+
return xdai_type(total_balance)
|
44
|
+
|
45
|
+
|
20
46
|
def store_trades(
|
21
47
|
market_id: str,
|
22
48
|
traded_market: ProcessedTradedMarket | None,
|
@@ -1,18 +1,19 @@
|
|
1
1
|
from enum import Enum
|
2
|
-
from typing import Annotated
|
2
|
+
from typing import Annotated, TypeAlias
|
3
3
|
|
4
4
|
from pydantic import BaseModel, BeforeValidator, computed_field
|
5
5
|
|
6
|
-
from prediction_market_agent_tooling.gtypes import
|
7
|
-
USD,
|
8
|
-
OutcomeStr,
|
9
|
-
OutcomeToken,
|
10
|
-
Probability,
|
11
|
-
Token,
|
12
|
-
)
|
6
|
+
from prediction_market_agent_tooling.gtypes import OutcomeStr, Probability
|
13
7
|
from prediction_market_agent_tooling.tools.utils import DatetimeUTC
|
14
8
|
|
15
9
|
|
10
|
+
class Currency(str, Enum):
|
11
|
+
xDai = "xDai"
|
12
|
+
sDai = "sDai"
|
13
|
+
Mana = "Mana"
|
14
|
+
USDC = "USDC"
|
15
|
+
|
16
|
+
|
16
17
|
class Resolution(str, Enum):
|
17
18
|
YES = "YES"
|
18
19
|
NO = "NO"
|
@@ -24,9 +25,21 @@ class Resolution(str, Enum):
|
|
24
25
|
return Resolution.YES if value else Resolution.NO
|
25
26
|
|
26
27
|
|
28
|
+
class TokenAmount(BaseModel):
|
29
|
+
amount: float
|
30
|
+
currency: Currency
|
31
|
+
|
32
|
+
def __str__(self) -> str:
|
33
|
+
return f"Amount {self.amount} currency {self.currency}"
|
34
|
+
|
35
|
+
|
36
|
+
BetAmount: TypeAlias = TokenAmount
|
37
|
+
ProfitAmount: TypeAlias = TokenAmount
|
38
|
+
|
39
|
+
|
27
40
|
class Bet(BaseModel):
|
28
41
|
id: str
|
29
|
-
amount:
|
42
|
+
amount: BetAmount
|
30
43
|
outcome: bool
|
31
44
|
created_time: DatetimeUTC
|
32
45
|
market_question: str
|
@@ -39,7 +52,7 @@ class Bet(BaseModel):
|
|
39
52
|
class ResolvedBet(Bet):
|
40
53
|
market_outcome: bool
|
41
54
|
resolved_time: DatetimeUTC
|
42
|
-
profit:
|
55
|
+
profit: ProfitAmount
|
43
56
|
|
44
57
|
@computed_field # type: ignore[prop-decorator]
|
45
58
|
@property
|
@@ -50,6 +63,10 @@ class ResolvedBet(Bet):
|
|
50
63
|
return f"Resolved bet for market {self.market_id} for question {self.market_question} created at {self.created_time}: {self.amount} on {self.outcome}. Bet was resolved at {self.resolved_time} and was {'correct' if self.is_correct else 'incorrect'}. Profit was {self.profit}"
|
51
64
|
|
52
65
|
|
66
|
+
class TokenAmountAndDirection(TokenAmount):
|
67
|
+
direction: bool
|
68
|
+
|
69
|
+
|
53
70
|
def to_boolean_outcome(value: str | bool) -> bool:
|
54
71
|
if isinstance(value, bool):
|
55
72
|
return value
|
@@ -85,36 +102,23 @@ class ProbabilisticAnswer(BaseModel):
|
|
85
102
|
|
86
103
|
class Position(BaseModel):
|
87
104
|
market_id: str
|
88
|
-
|
89
|
-
amounts_current: dict[OutcomeStr, USD]
|
105
|
+
amounts: dict[OutcomeStr, TokenAmount]
|
90
106
|
|
91
107
|
@property
|
92
|
-
def
|
93
|
-
return
|
108
|
+
def total_amount(self) -> TokenAmount:
|
109
|
+
return TokenAmount(
|
110
|
+
amount=sum(amount.amount for amount in self.amounts.values()),
|
111
|
+
currency=self.amounts[next(iter(self.amounts.keys()))].currency,
|
112
|
+
)
|
94
113
|
|
95
114
|
def __str__(self) -> str:
|
96
115
|
amounts_str = ", ".join(
|
97
|
-
f"{amount}
|
98
|
-
for outcome, amount in self.
|
116
|
+
f"{amount.amount} '{outcome}' tokens"
|
117
|
+
for outcome, amount in self.amounts.items()
|
99
118
|
)
|
100
119
|
return f"Position for market id {self.market_id}: {amounts_str}"
|
101
120
|
|
102
121
|
|
103
|
-
class ExistingPosition(Position):
|
104
|
-
# This is how much we will get if we win.
|
105
|
-
amounts_potential: dict[OutcomeStr, USD]
|
106
|
-
# These are raw outcome tokens of the market.
|
107
|
-
amounts_ot: dict[OutcomeStr, OutcomeToken]
|
108
|
-
|
109
|
-
@property
|
110
|
-
def total_amount_potential(self) -> USD:
|
111
|
-
return sum(self.amounts_potential.values(), start=USD(0))
|
112
|
-
|
113
|
-
@property
|
114
|
-
def total_amount_ot(self) -> OutcomeToken:
|
115
|
-
return sum(self.amounts_ot.values(), start=OutcomeToken(0))
|
116
|
-
|
117
|
-
|
118
122
|
class TradeType(str, Enum):
|
119
123
|
SELL = "sell"
|
120
124
|
BUY = "buy"
|
@@ -123,7 +127,7 @@ class TradeType(str, Enum):
|
|
123
127
|
class Trade(BaseModel):
|
124
128
|
trade_type: TradeType
|
125
129
|
outcome: bool
|
126
|
-
amount:
|
130
|
+
amount: TokenAmount
|
127
131
|
|
128
132
|
|
129
133
|
class PlacedTrade(Trade):
|
@@ -145,12 +149,12 @@ class SimulatedBetDetail(BaseModel):
|
|
145
149
|
market_p_yes: float
|
146
150
|
agent_p_yes: float
|
147
151
|
agent_conf: float
|
148
|
-
org_bet:
|
149
|
-
sim_bet:
|
152
|
+
org_bet: float
|
153
|
+
sim_bet: float
|
150
154
|
org_dir: bool
|
151
155
|
sim_dir: bool
|
152
|
-
org_profit:
|
153
|
-
sim_profit:
|
156
|
+
org_profit: float
|
157
|
+
sim_profit: float
|
154
158
|
timestamp: DatetimeUTC
|
155
159
|
|
156
160
|
|
@@ -162,10 +166,10 @@ class SharpeOutput(BaseModel):
|
|
162
166
|
|
163
167
|
class SimulatedLifetimeDetail(BaseModel):
|
164
168
|
p_yes_mse: float
|
165
|
-
total_bet_amount:
|
166
|
-
total_bet_profit:
|
167
|
-
total_simulated_amount:
|
168
|
-
total_simulated_profit:
|
169
|
+
total_bet_amount: float
|
170
|
+
total_bet_profit: float
|
171
|
+
total_simulated_amount: float
|
172
|
+
total_simulated_profit: float
|
169
173
|
roi: float
|
170
174
|
simulated_roi: float
|
171
175
|
sharpe_output_original: SharpeOutput
|
@@ -5,7 +5,11 @@ import tenacity
|
|
5
5
|
|
6
6
|
from prediction_market_agent_tooling.gtypes import Mana, SecretStr
|
7
7
|
from prediction_market_agent_tooling.loggers import logger
|
8
|
-
from prediction_market_agent_tooling.markets.data_models import
|
8
|
+
from prediction_market_agent_tooling.markets.data_models import (
|
9
|
+
BetAmount,
|
10
|
+
Currency,
|
11
|
+
ResolvedBet,
|
12
|
+
)
|
9
13
|
from prediction_market_agent_tooling.markets.manifold.data_models import (
|
10
14
|
FullManifoldMarket,
|
11
15
|
ManifoldBet,
|
@@ -207,7 +211,7 @@ def manifold_to_generic_resolved_bet(
|
|
207
211
|
market_outcome = market.get_resolved_boolean_outcome()
|
208
212
|
return ResolvedBet(
|
209
213
|
id=bet.id,
|
210
|
-
amount=bet.amount,
|
214
|
+
amount=BetAmount(amount=bet.amount, currency=Currency.Mana),
|
211
215
|
outcome=bet.get_resolved_boolean_outcome(),
|
212
216
|
created_time=bet.createdTime,
|
213
217
|
market_question=market.question,
|
@@ -3,37 +3,24 @@ from enum import Enum
|
|
3
3
|
|
4
4
|
from pydantic import BaseModel
|
5
5
|
|
6
|
-
from prediction_market_agent_tooling.gtypes import
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
Probability,
|
12
|
-
Token,
|
6
|
+
from prediction_market_agent_tooling.gtypes import Mana, Probability
|
7
|
+
from prediction_market_agent_tooling.markets.data_models import (
|
8
|
+
Currency,
|
9
|
+
ProfitAmount,
|
10
|
+
Resolution,
|
13
11
|
)
|
14
|
-
from prediction_market_agent_tooling.markets.data_models import Resolution
|
15
12
|
from prediction_market_agent_tooling.tools.utils import DatetimeUTC, should_not_happen
|
16
13
|
|
17
14
|
MANIFOLD_BASE_URL = "https://manifold.markets"
|
18
15
|
|
19
16
|
|
20
|
-
def mana_to_usd(mana: Mana) -> USD:
|
21
|
-
# Not really, but for sake of simplicity. Mana are just play money.
|
22
|
-
return USD(mana.value)
|
23
|
-
|
24
|
-
|
25
|
-
def usd_to_mana(usd: USD) -> Mana:
|
26
|
-
# Not really, but for sake of simplicity. Mana are just play money.
|
27
|
-
return Mana(usd.value)
|
28
|
-
|
29
|
-
|
30
17
|
class ManifoldPool(BaseModel):
|
31
|
-
NO:
|
32
|
-
YES:
|
18
|
+
NO: float
|
19
|
+
YES: float
|
33
20
|
|
34
|
-
def size_for_outcome(self, outcome: str) ->
|
21
|
+
def size_for_outcome(self, outcome: str) -> float:
|
35
22
|
if hasattr(self, outcome):
|
36
|
-
return
|
23
|
+
return float(getattr(self, outcome))
|
37
24
|
else:
|
38
25
|
should_not_happen(f"Unexpected outcome string, '{outcome}'.")
|
39
26
|
|
@@ -62,6 +49,8 @@ class ManifoldMarket(BaseModel):
|
|
62
49
|
https://docs.manifold.markets/api#get-v0markets
|
63
50
|
"""
|
64
51
|
|
52
|
+
BET_AMOUNT_CURRENCY: t.ClassVar[Currency] = Currency.Mana
|
53
|
+
|
65
54
|
id: str
|
66
55
|
question: str
|
67
56
|
creatorId: str
|
@@ -82,15 +71,15 @@ class ManifoldMarket(BaseModel):
|
|
82
71
|
pool: ManifoldPool
|
83
72
|
probability: Probability
|
84
73
|
slug: str
|
85
|
-
totalLiquidity: t.Optional[
|
74
|
+
totalLiquidity: t.Optional[Mana] = None
|
86
75
|
uniqueBettorCount: int
|
87
76
|
url: str
|
88
|
-
volume:
|
89
|
-
volume24Hours:
|
77
|
+
volume: Mana
|
78
|
+
volume24Hours: Mana
|
90
79
|
|
91
80
|
@property
|
92
|
-
def outcomes(self) ->
|
93
|
-
return
|
81
|
+
def outcomes(self) -> list[str]:
|
82
|
+
return list(self.pool.model_fields.keys())
|
94
83
|
|
95
84
|
def get_resolved_boolean_outcome(self) -> bool:
|
96
85
|
if self.resolution == Resolution.YES:
|
@@ -181,18 +170,18 @@ class ManifoldBet(BaseModel):
|
|
181
170
|
https://docs.manifold.markets/api#get-v0bets
|
182
171
|
"""
|
183
172
|
|
184
|
-
shares:
|
173
|
+
shares: float
|
185
174
|
probBefore: Probability
|
186
175
|
isFilled: t.Optional[bool] = None
|
187
176
|
probAfter: Probability
|
188
177
|
userId: str
|
189
|
-
amount:
|
178
|
+
amount: Mana
|
190
179
|
contractId: str
|
191
180
|
id: str
|
192
181
|
fees: ManifoldBetFees
|
193
182
|
isCancelled: t.Optional[bool] = None
|
194
|
-
loanAmount:
|
195
|
-
orderAmount: t.Optional[
|
183
|
+
loanAmount: Mana | None
|
184
|
+
orderAmount: t.Optional[Mana] = None
|
196
185
|
fills: t.Optional[list[ManifoldBetFills]] = None
|
197
186
|
createdTime: DatetimeUTC
|
198
187
|
outcome: Resolution
|
@@ -205,13 +194,16 @@ class ManifoldBet(BaseModel):
|
|
205
194
|
else:
|
206
195
|
should_not_happen(f"Unexpected bet outcome string, '{self.outcome.value}'.")
|
207
196
|
|
208
|
-
def get_profit(self, market_outcome: bool) ->
|
197
|
+
def get_profit(self, market_outcome: bool) -> ProfitAmount:
|
209
198
|
profit = (
|
210
199
|
self.shares - self.amount
|
211
200
|
if self.get_resolved_boolean_outcome() == market_outcome
|
212
201
|
else -self.amount
|
213
202
|
)
|
214
|
-
return
|
203
|
+
return ProfitAmount(
|
204
|
+
amount=profit,
|
205
|
+
currency=Currency.Mana,
|
206
|
+
)
|
215
207
|
|
216
208
|
|
217
209
|
class ManifoldContractMetric(BaseModel):
|
@@ -2,13 +2,14 @@ import typing as t
|
|
2
2
|
from math import ceil
|
3
3
|
|
4
4
|
from prediction_market_agent_tooling.config import APIKeys
|
5
|
-
from prediction_market_agent_tooling.gtypes import
|
5
|
+
from prediction_market_agent_tooling.gtypes import Mana, Probability, mana_type
|
6
6
|
from prediction_market_agent_tooling.markets.agent_market import (
|
7
7
|
AgentMarket,
|
8
8
|
FilterBy,
|
9
9
|
MarketFees,
|
10
10
|
SortBy,
|
11
11
|
)
|
12
|
+
from prediction_market_agent_tooling.markets.data_models import BetAmount, Currency
|
12
13
|
from prediction_market_agent_tooling.markets.manifold.api import (
|
13
14
|
get_authenticated_user,
|
14
15
|
get_manifold_binary_markets,
|
@@ -18,7 +19,6 @@ from prediction_market_agent_tooling.markets.manifold.api import (
|
|
18
19
|
from prediction_market_agent_tooling.markets.manifold.data_models import (
|
19
20
|
MANIFOLD_BASE_URL,
|
20
21
|
FullManifoldMarket,
|
21
|
-
usd_to_mana,
|
22
22
|
)
|
23
23
|
from prediction_market_agent_tooling.tools.betting_strategies.minimum_bet_to_win import (
|
24
24
|
minimum_bet_to_win,
|
@@ -31,6 +31,7 @@ class ManifoldAgentMarket(AgentMarket):
|
|
31
31
|
Manifold's market class that can be used by agents to make predictions.
|
32
32
|
"""
|
33
33
|
|
34
|
+
currency: t.ClassVar[Currency] = Currency.Mana
|
34
35
|
base_url: t.ClassVar[str] = MANIFOLD_BASE_URL
|
35
36
|
|
36
37
|
# Manifold has additional fees than `platform_absolute`, but they don't expose them in the API before placing the bet, see https://docs.manifold.markets/api.
|
@@ -48,17 +49,19 @@ class ManifoldAgentMarket(AgentMarket):
|
|
48
49
|
"""On Manifold, probablities aren't updated after the closure, so we can just use the current probability"""
|
49
50
|
return self.current_p_no
|
50
51
|
|
51
|
-
|
52
|
-
|
52
|
+
@classmethod
|
53
|
+
def get_tiny_bet_amount(cls) -> BetAmount:
|
54
|
+
return BetAmount(amount=1, currency=cls.currency)
|
53
55
|
|
54
56
|
def get_minimum_bet_to_win(self, answer: bool, amount_to_win: float) -> Mana:
|
55
57
|
# Manifold lowest bet is 1 Mana, so we need to ceil the result.
|
56
|
-
return
|
58
|
+
return mana_type(ceil(minimum_bet_to_win(answer, amount_to_win, self)))
|
57
59
|
|
58
|
-
def place_bet(self, outcome: bool, amount:
|
59
|
-
self.
|
60
|
+
def place_bet(self, outcome: bool, amount: BetAmount) -> str:
|
61
|
+
if amount.currency != self.currency:
|
62
|
+
raise ValueError(f"Manifold bets are made in Mana. Got {amount.currency}.")
|
60
63
|
bet = place_bet(
|
61
|
-
amount=
|
64
|
+
amount=Mana(amount.amount),
|
62
65
|
market_id=self.id,
|
63
66
|
outcome=outcome,
|
64
67
|
manifold_api_key=APIKeys().manifold_api_key,
|