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