prediction-market-agent-tooling 0.65.5__py3-none-any.whl → 0.69.17.dev1149__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/abis/agentresultmapping.abi.json +192 -0
- prediction_market_agent_tooling/abis/erc1155.abi.json +352 -0
- prediction_market_agent_tooling/abis/processor.abi.json +16 -0
- prediction_market_agent_tooling/abis/swapr_quoter.abi.json +221 -0
- prediction_market_agent_tooling/abis/swapr_router.abi.json +634 -0
- prediction_market_agent_tooling/benchmark/benchmark.py +1 -1
- prediction_market_agent_tooling/benchmark/utils.py +13 -0
- prediction_market_agent_tooling/chains.py +1 -0
- prediction_market_agent_tooling/config.py +61 -2
- prediction_market_agent_tooling/data_download/langfuse_data_downloader.py +405 -0
- prediction_market_agent_tooling/deploy/agent.py +199 -67
- prediction_market_agent_tooling/deploy/agent_example.py +1 -1
- prediction_market_agent_tooling/deploy/betting_strategy.py +412 -68
- prediction_market_agent_tooling/deploy/constants.py +6 -0
- prediction_market_agent_tooling/gtypes.py +11 -1
- prediction_market_agent_tooling/jobs/jobs_models.py +2 -2
- prediction_market_agent_tooling/jobs/omen/omen_jobs.py +19 -20
- prediction_market_agent_tooling/loggers.py +9 -1
- prediction_market_agent_tooling/logprobs_parser.py +2 -1
- prediction_market_agent_tooling/markets/agent_market.py +106 -18
- prediction_market_agent_tooling/markets/blockchain_utils.py +37 -19
- prediction_market_agent_tooling/markets/data_models.py +120 -7
- prediction_market_agent_tooling/markets/manifold/data_models.py +5 -3
- prediction_market_agent_tooling/markets/manifold/manifold.py +21 -2
- prediction_market_agent_tooling/markets/manifold/utils.py +8 -2
- prediction_market_agent_tooling/markets/market_type.py +74 -0
- prediction_market_agent_tooling/markets/markets.py +7 -99
- prediction_market_agent_tooling/markets/metaculus/data_models.py +3 -3
- prediction_market_agent_tooling/markets/metaculus/metaculus.py +5 -8
- prediction_market_agent_tooling/markets/omen/cow_contracts.py +5 -1
- prediction_market_agent_tooling/markets/omen/data_models.py +63 -32
- prediction_market_agent_tooling/markets/omen/omen.py +112 -23
- prediction_market_agent_tooling/markets/omen/omen_constants.py +8 -0
- prediction_market_agent_tooling/markets/omen/omen_contracts.py +18 -203
- prediction_market_agent_tooling/markets/omen/omen_resolving.py +33 -13
- prediction_market_agent_tooling/markets/omen/omen_subgraph_handler.py +23 -18
- prediction_market_agent_tooling/markets/polymarket/api.py +123 -100
- prediction_market_agent_tooling/markets/polymarket/clob_manager.py +156 -0
- prediction_market_agent_tooling/markets/polymarket/constants.py +15 -0
- prediction_market_agent_tooling/markets/polymarket/data_models.py +95 -19
- prediction_market_agent_tooling/markets/polymarket/polymarket.py +373 -29
- prediction_market_agent_tooling/markets/polymarket/polymarket_contracts.py +35 -0
- prediction_market_agent_tooling/markets/polymarket/polymarket_subgraph_handler.py +91 -0
- prediction_market_agent_tooling/markets/polymarket/utils.py +1 -22
- prediction_market_agent_tooling/markets/seer/data_models.py +111 -17
- prediction_market_agent_tooling/markets/seer/exceptions.py +2 -0
- prediction_market_agent_tooling/markets/seer/price_manager.py +165 -50
- prediction_market_agent_tooling/markets/seer/seer.py +393 -106
- prediction_market_agent_tooling/markets/seer/seer_api.py +28 -0
- prediction_market_agent_tooling/markets/seer/seer_contracts.py +115 -5
- prediction_market_agent_tooling/markets/seer/seer_subgraph_handler.py +297 -66
- prediction_market_agent_tooling/markets/seer/subgraph_data_models.py +43 -8
- prediction_market_agent_tooling/markets/seer/swap_pool_handler.py +80 -0
- prediction_market_agent_tooling/tools/_generic_value.py +8 -2
- prediction_market_agent_tooling/tools/betting_strategies/kelly_criterion.py +271 -8
- prediction_market_agent_tooling/tools/betting_strategies/utils.py +6 -1
- prediction_market_agent_tooling/tools/caches/db_cache.py +219 -117
- prediction_market_agent_tooling/tools/caches/serializers.py +11 -2
- prediction_market_agent_tooling/tools/contract.py +480 -38
- prediction_market_agent_tooling/tools/contract_utils.py +61 -0
- prediction_market_agent_tooling/tools/cow/cow_order.py +218 -45
- prediction_market_agent_tooling/tools/cow/models.py +122 -0
- prediction_market_agent_tooling/tools/cow/semaphore.py +104 -0
- prediction_market_agent_tooling/tools/datetime_utc.py +14 -2
- prediction_market_agent_tooling/tools/db/db_manager.py +59 -0
- prediction_market_agent_tooling/tools/hexbytes_custom.py +4 -1
- prediction_market_agent_tooling/tools/httpx_cached_client.py +15 -6
- prediction_market_agent_tooling/tools/langfuse_client_utils.py +21 -8
- prediction_market_agent_tooling/tools/openai_utils.py +31 -0
- prediction_market_agent_tooling/tools/perplexity/perplexity_client.py +86 -0
- prediction_market_agent_tooling/tools/perplexity/perplexity_models.py +26 -0
- prediction_market_agent_tooling/tools/perplexity/perplexity_search.py +73 -0
- prediction_market_agent_tooling/tools/rephrase.py +71 -0
- prediction_market_agent_tooling/tools/singleton.py +11 -6
- prediction_market_agent_tooling/tools/streamlit_utils.py +188 -0
- prediction_market_agent_tooling/tools/tokens/auto_deposit.py +64 -0
- prediction_market_agent_tooling/tools/tokens/auto_withdraw.py +8 -0
- prediction_market_agent_tooling/tools/tokens/slippage.py +21 -0
- prediction_market_agent_tooling/tools/tokens/usd.py +5 -2
- prediction_market_agent_tooling/tools/utils.py +61 -3
- prediction_market_agent_tooling/tools/web3_utils.py +63 -9
- {prediction_market_agent_tooling-0.65.5.dist-info → prediction_market_agent_tooling-0.69.17.dev1149.dist-info}/METADATA +13 -9
- {prediction_market_agent_tooling-0.65.5.dist-info → prediction_market_agent_tooling-0.69.17.dev1149.dist-info}/RECORD +86 -64
- {prediction_market_agent_tooling-0.65.5.dist-info → prediction_market_agent_tooling-0.69.17.dev1149.dist-info}/WHEEL +1 -1
- prediction_market_agent_tooling/abis/omen_agentresultmapping.abi.json +0 -171
- prediction_market_agent_tooling/markets/polymarket/data_models_web.py +0 -420
- {prediction_market_agent_tooling-0.65.5.dist-info → prediction_market_agent_tooling-0.69.17.dev1149.dist-info}/entry_points.txt +0 -0
- {prediction_market_agent_tooling-0.65.5.dist-info → prediction_market_agent_tooling-0.69.17.dev1149.dist-info/licenses}/LICENSE +0 -0
|
@@ -6,11 +6,12 @@ from enum import Enum
|
|
|
6
6
|
from functools import cached_property
|
|
7
7
|
|
|
8
8
|
from pydantic import computed_field
|
|
9
|
+
from pydantic_ai.exceptions import UnexpectedModelBehavior
|
|
9
10
|
|
|
10
11
|
from prediction_market_agent_tooling.config import APIKeys
|
|
11
12
|
from prediction_market_agent_tooling.deploy.betting_strategy import (
|
|
12
13
|
BettingStrategy,
|
|
13
|
-
|
|
14
|
+
CategoricalMaxAccuracyBettingStrategy,
|
|
14
15
|
TradeType,
|
|
15
16
|
)
|
|
16
17
|
from prediction_market_agent_tooling.deploy.trade_interval import (
|
|
@@ -21,9 +22,10 @@ from prediction_market_agent_tooling.gtypes import USD, OutcomeToken, xDai
|
|
|
21
22
|
from prediction_market_agent_tooling.loggers import logger
|
|
22
23
|
from prediction_market_agent_tooling.markets.agent_market import (
|
|
23
24
|
AgentMarket,
|
|
25
|
+
ConditionalFilterType,
|
|
24
26
|
FilterBy,
|
|
25
27
|
ProcessedMarket,
|
|
26
|
-
|
|
28
|
+
QuestionType,
|
|
27
29
|
SortBy,
|
|
28
30
|
)
|
|
29
31
|
from prediction_market_agent_tooling.markets.data_models import (
|
|
@@ -31,15 +33,10 @@ from prediction_market_agent_tooling.markets.data_models import (
|
|
|
31
33
|
ExistingPosition,
|
|
32
34
|
PlacedTrade,
|
|
33
35
|
ProbabilisticAnswer,
|
|
36
|
+
ScalarProbabilisticAnswer,
|
|
34
37
|
Trade,
|
|
35
38
|
)
|
|
36
|
-
from prediction_market_agent_tooling.markets.
|
|
37
|
-
MarketType,
|
|
38
|
-
have_bet_on_market_since,
|
|
39
|
-
)
|
|
40
|
-
from prediction_market_agent_tooling.markets.omen.omen import (
|
|
41
|
-
send_keeping_token_to_eoa_xdai,
|
|
42
|
-
)
|
|
39
|
+
from prediction_market_agent_tooling.markets.market_type import MarketType
|
|
43
40
|
from prediction_market_agent_tooling.tools.custom_exceptions import (
|
|
44
41
|
CantPayForGasError,
|
|
45
42
|
OutOfFundsError,
|
|
@@ -47,12 +44,16 @@ from prediction_market_agent_tooling.tools.custom_exceptions import (
|
|
|
47
44
|
from prediction_market_agent_tooling.tools.is_invalid import is_invalid
|
|
48
45
|
from prediction_market_agent_tooling.tools.is_predictable import is_predictable_binary
|
|
49
46
|
from prediction_market_agent_tooling.tools.langfuse_ import langfuse_context, observe
|
|
47
|
+
from prediction_market_agent_tooling.tools.rephrase import (
|
|
48
|
+
rephrase_question_to_unconditional,
|
|
49
|
+
)
|
|
50
50
|
from prediction_market_agent_tooling.tools.tokens.main_token import (
|
|
51
51
|
MINIMUM_NATIVE_TOKEN_IN_EOA_FOR_FEES,
|
|
52
52
|
)
|
|
53
53
|
from prediction_market_agent_tooling.tools.utils import (
|
|
54
54
|
DatetimeUTC,
|
|
55
55
|
check_not_none,
|
|
56
|
+
retry_until_true,
|
|
56
57
|
utcnow,
|
|
57
58
|
)
|
|
58
59
|
|
|
@@ -196,6 +197,7 @@ class DeployablePredictionAgent(DeployableAgent):
|
|
|
196
197
|
trade_on_markets_created_after: DatetimeUTC | None = None
|
|
197
198
|
get_markets_sort_by: SortBy = SortBy.CLOSING_SOONEST
|
|
198
199
|
get_markets_filter_by: FilterBy = FilterBy.OPEN
|
|
200
|
+
rephrase_conditional_markets: bool = True
|
|
199
201
|
|
|
200
202
|
# Agent behaviour when filtering fetched markets
|
|
201
203
|
allow_invalid_questions: bool = False
|
|
@@ -205,6 +207,8 @@ class DeployablePredictionAgent(DeployableAgent):
|
|
|
205
207
|
MINIMUM_NATIVE_TOKEN_IN_EOA_FOR_FEES
|
|
206
208
|
)
|
|
207
209
|
|
|
210
|
+
just_warn_on_unexpected_model_behavior: bool = False
|
|
211
|
+
|
|
208
212
|
# Only Metaculus allows to post predictions without trading (buying/selling of outcome tokens).
|
|
209
213
|
supported_markets: t.Sequence[MarketType] = [MarketType.METACULUS]
|
|
210
214
|
|
|
@@ -219,11 +223,12 @@ class DeployablePredictionAgent(DeployableAgent):
|
|
|
219
223
|
def initialize_langfuse(self) -> None:
|
|
220
224
|
super().initialize_langfuse()
|
|
221
225
|
# Auto-observe all the methods where it makes sense, so that subclassses don't need to do it manually.
|
|
222
|
-
self.have_bet_on_market_since = observe()(self.have_bet_on_market_since) # type: ignore[method-assign]
|
|
223
226
|
self.verify_market = observe()(self.verify_market) # type: ignore[method-assign]
|
|
224
227
|
self.answer_binary_market = observe()(self.answer_binary_market) # type: ignore[method-assign]
|
|
225
228
|
self.answer_categorical_market = observe()(self.answer_categorical_market) # type: ignore[method-assign]
|
|
229
|
+
self.answer_scalar_market = observe()(self.answer_scalar_market) # type: ignore[method-assign]
|
|
226
230
|
self.process_market = observe()(self.process_market) # type: ignore[method-assign]
|
|
231
|
+
self.rephrase_market_to_unconditional = observe()(self.rephrase_market_to_unconditional) # type: ignore[method-assign]
|
|
227
232
|
|
|
228
233
|
def update_langfuse_trace_by_market(
|
|
229
234
|
self, market_type: MarketType, market: AgentMarket
|
|
@@ -265,16 +270,13 @@ class DeployablePredictionAgent(DeployableAgent):
|
|
|
265
270
|
f"{api_keys=} doesn't have enough operational balance."
|
|
266
271
|
)
|
|
267
272
|
|
|
268
|
-
def have_bet_on_market_since(self, market: AgentMarket, since: timedelta) -> bool:
|
|
269
|
-
return have_bet_on_market_since(keys=APIKeys(), market=market, since=since)
|
|
270
|
-
|
|
271
273
|
def verify_market(self, market_type: MarketType, market: AgentMarket) -> bool:
|
|
272
274
|
"""
|
|
273
275
|
Subclasses can implement their own logic instead of this one, or on top of this one.
|
|
274
276
|
By default, it allows only markets where user didn't bet recently and it's a reasonable question.
|
|
275
277
|
"""
|
|
276
|
-
if
|
|
277
|
-
|
|
278
|
+
if market.have_bet_on_market_since(
|
|
279
|
+
keys=APIKeys(), since=self.same_market_trade_interval.get(market=market)
|
|
278
280
|
):
|
|
279
281
|
logger.info(
|
|
280
282
|
f"Market already bet on within {self.same_market_trade_interval}."
|
|
@@ -301,11 +303,44 @@ class DeployablePredictionAgent(DeployableAgent):
|
|
|
301
303
|
|
|
302
304
|
return True
|
|
303
305
|
|
|
306
|
+
def rephrase_market_to_unconditional(
|
|
307
|
+
self,
|
|
308
|
+
market_: AgentMarket,
|
|
309
|
+
) -> AgentMarket:
|
|
310
|
+
"""
|
|
311
|
+
If `rephrase_conditional_markets` is set to True,
|
|
312
|
+
this method will be used to rephrase the question to account for the parent's market probability in the agent's decision process.
|
|
313
|
+
"""
|
|
314
|
+
new = market_.model_copy(deep=True)
|
|
315
|
+
|
|
316
|
+
if new.parent is not None and new.parent.market.parent is not None:
|
|
317
|
+
new.parent.market = self.rephrase_market_to_unconditional(new.parent.market)
|
|
318
|
+
|
|
319
|
+
rephrased_question = (
|
|
320
|
+
rephrase_question_to_unconditional(
|
|
321
|
+
new.question,
|
|
322
|
+
new.parent.market.question,
|
|
323
|
+
new.parent.market.outcomes[new.parent.parent_outcome],
|
|
324
|
+
)
|
|
325
|
+
if new.parent is not None
|
|
326
|
+
else new.question
|
|
327
|
+
)
|
|
328
|
+
|
|
329
|
+
new.question = rephrased_question
|
|
330
|
+
new.parent = None
|
|
331
|
+
|
|
332
|
+
return new
|
|
333
|
+
|
|
304
334
|
def answer_categorical_market(
|
|
305
335
|
self, market: AgentMarket
|
|
306
336
|
) -> CategoricalProbabilisticAnswer | None:
|
|
307
337
|
raise NotImplementedError("This method must be implemented by the subclass")
|
|
308
338
|
|
|
339
|
+
def answer_scalar_market(
|
|
340
|
+
self, market: AgentMarket
|
|
341
|
+
) -> ScalarProbabilisticAnswer | None:
|
|
342
|
+
raise NotImplementedError("This method must be implemented by the subclass")
|
|
343
|
+
|
|
309
344
|
def answer_binary_market(self, market: AgentMarket) -> ProbabilisticAnswer | None:
|
|
310
345
|
"""
|
|
311
346
|
Answer the binary market.
|
|
@@ -328,6 +363,28 @@ class DeployablePredictionAgent(DeployableAgent):
|
|
|
328
363
|
return True
|
|
329
364
|
return False
|
|
330
365
|
|
|
366
|
+
@property
|
|
367
|
+
def fetch_scalar_markets(self) -> bool:
|
|
368
|
+
# Check if the subclass has implemented the answer_scalar_market method, if yes, fetch scalar markets as well.
|
|
369
|
+
if self.answer_scalar_market.__wrapped__.__func__ is not DeployablePredictionAgent.answer_scalar_market: # type: ignore[attr-defined] # This works just fine, but mypy doesn't know about it for some reason.
|
|
370
|
+
return True
|
|
371
|
+
return False
|
|
372
|
+
|
|
373
|
+
@property
|
|
374
|
+
def conditional_filter_type(self) -> ConditionalFilterType:
|
|
375
|
+
if self.rephrase_conditional_markets:
|
|
376
|
+
return ConditionalFilterType.ALL
|
|
377
|
+
return ConditionalFilterType.ONLY_NOT_CONDITIONAL
|
|
378
|
+
|
|
379
|
+
@property
|
|
380
|
+
def agent_question_type(self) -> QuestionType:
|
|
381
|
+
if self.fetch_scalar_markets:
|
|
382
|
+
return QuestionType.SCALAR
|
|
383
|
+
elif self.fetch_categorical_markets:
|
|
384
|
+
return QuestionType.CATEGORICAL
|
|
385
|
+
else:
|
|
386
|
+
return QuestionType.BINARY
|
|
387
|
+
|
|
331
388
|
def get_markets(
|
|
332
389
|
self,
|
|
333
390
|
market_type: MarketType,
|
|
@@ -336,13 +393,15 @@ class DeployablePredictionAgent(DeployableAgent):
|
|
|
336
393
|
Override this method to customize what markets will fetch for processing.
|
|
337
394
|
"""
|
|
338
395
|
cls = market_type.market_class
|
|
396
|
+
|
|
339
397
|
# Fetch the soonest closing markets to choose from
|
|
340
398
|
available_markets = cls.get_markets(
|
|
341
399
|
limit=self.n_markets_to_fetch,
|
|
342
400
|
sort_by=self.get_markets_sort_by,
|
|
343
401
|
filter_by=self.get_markets_filter_by,
|
|
344
402
|
created_after=self.trade_on_markets_created_after,
|
|
345
|
-
|
|
403
|
+
question_type=self.agent_question_type,
|
|
404
|
+
conditional_filter_type=self.conditional_filter_type,
|
|
346
405
|
)
|
|
347
406
|
return available_markets
|
|
348
407
|
|
|
@@ -352,15 +411,13 @@ class DeployablePredictionAgent(DeployableAgent):
|
|
|
352
411
|
"""
|
|
353
412
|
Executed before processing of each market.
|
|
354
413
|
"""
|
|
355
|
-
api_keys = APIKeys()
|
|
356
414
|
|
|
357
415
|
if market_type.is_blockchain_market:
|
|
358
|
-
#
|
|
416
|
+
# Ensure we have enough native token balance for transaction fees
|
|
359
417
|
if self.min_balance_to_keep_in_native_currency is not None:
|
|
360
|
-
|
|
361
|
-
api_keys,
|
|
418
|
+
market.ensure_min_native_balance(
|
|
362
419
|
min_required_balance=self.min_balance_to_keep_in_native_currency,
|
|
363
|
-
multiplier=3,
|
|
420
|
+
multiplier=3.0,
|
|
364
421
|
)
|
|
365
422
|
|
|
366
423
|
def build_answer(
|
|
@@ -375,12 +432,16 @@ class DeployablePredictionAgent(DeployableAgent):
|
|
|
375
432
|
|
|
376
433
|
logger.info(f"Answering market '{market.question}'.")
|
|
377
434
|
|
|
435
|
+
if self.rephrase_conditional_markets and market.parent is not None:
|
|
436
|
+
market = self.rephrase_market_to_unconditional(market)
|
|
437
|
+
|
|
378
438
|
if market.is_binary:
|
|
379
439
|
try:
|
|
380
440
|
binary_answer = self.answer_binary_market(market)
|
|
381
441
|
return (
|
|
382
442
|
CategoricalProbabilisticAnswer.from_probabilistic_answer(
|
|
383
|
-
binary_answer
|
|
443
|
+
binary_answer,
|
|
444
|
+
market.outcomes,
|
|
384
445
|
)
|
|
385
446
|
if binary_answer is not None
|
|
386
447
|
else None
|
|
@@ -389,9 +450,39 @@ class DeployablePredictionAgent(DeployableAgent):
|
|
|
389
450
|
logger.info(
|
|
390
451
|
"answer_binary_market() not implemented, falling back to answer_categorical_market()"
|
|
391
452
|
)
|
|
392
|
-
|
|
453
|
+
elif market.is_scalar:
|
|
454
|
+
scalar_answer = self.answer_scalar_market(market)
|
|
455
|
+
return (
|
|
456
|
+
CategoricalProbabilisticAnswer.from_scalar_answer(
|
|
457
|
+
scalar_answer,
|
|
458
|
+
market.outcomes,
|
|
459
|
+
)
|
|
460
|
+
if scalar_answer is not None
|
|
461
|
+
else None
|
|
462
|
+
)
|
|
393
463
|
return self.answer_categorical_market(market)
|
|
394
464
|
|
|
465
|
+
def verify_answer_outcomes(
|
|
466
|
+
self, market: AgentMarket, answer: CategoricalProbabilisticAnswer
|
|
467
|
+
) -> None:
|
|
468
|
+
outcomes_from_prob_map = list(answer.probabilities.keys())
|
|
469
|
+
|
|
470
|
+
if any(
|
|
471
|
+
outcome_from_answer not in market.outcomes
|
|
472
|
+
for outcome_from_answer in outcomes_from_prob_map
|
|
473
|
+
):
|
|
474
|
+
raise ValueError(
|
|
475
|
+
f"Some of generated outcomes ({outcomes_from_prob_map=}) in probability map doesn't match with market's outcomes ({market.outcomes=})."
|
|
476
|
+
)
|
|
477
|
+
|
|
478
|
+
if any(
|
|
479
|
+
market_outcome not in outcomes_from_prob_map
|
|
480
|
+
for market_outcome in market.outcomes
|
|
481
|
+
):
|
|
482
|
+
logger.warning(
|
|
483
|
+
f"Some of market's outcomes ({market.outcomes=}) isn't included in the probability map ({outcomes_from_prob_map=})."
|
|
484
|
+
)
|
|
485
|
+
|
|
395
486
|
def process_market(
|
|
396
487
|
self,
|
|
397
488
|
market_type: MarketType,
|
|
@@ -403,17 +494,28 @@ class DeployablePredictionAgent(DeployableAgent):
|
|
|
403
494
|
f"Processing market {market.question=} from {market.url=} with liquidity {market.get_liquidity()}."
|
|
404
495
|
)
|
|
405
496
|
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
497
|
+
try:
|
|
498
|
+
answer = self.build_answer(
|
|
499
|
+
market=market, market_type=market_type, verify_market=verify_market
|
|
500
|
+
)
|
|
501
|
+
except UnexpectedModelBehavior:
|
|
502
|
+
(
|
|
503
|
+
logger.warning
|
|
504
|
+
if self.just_warn_on_unexpected_model_behavior
|
|
505
|
+
else logger.exception
|
|
506
|
+
)(f"Unexpected model behaviour in {self.__class__.__name__}.")
|
|
507
|
+
answer = None
|
|
508
|
+
|
|
509
|
+
if answer is not None:
|
|
510
|
+
self.verify_answer_outcomes(market=market, answer=answer)
|
|
409
511
|
|
|
410
512
|
processed_market = (
|
|
411
|
-
ProcessedMarket(answer=answer) if answer is not None else None
|
|
513
|
+
ProcessedMarket(answer=answer, trades=[]) if answer is not None else None
|
|
412
514
|
)
|
|
413
515
|
|
|
414
516
|
self.update_langfuse_trace_by_processed_market(market_type, processed_market)
|
|
415
517
|
logger.info(
|
|
416
|
-
f"Processed market {market.question=} from {market.url=} with {
|
|
518
|
+
f"Processed market {market.question=} from {market.url=} with {processed_market=}."
|
|
417
519
|
)
|
|
418
520
|
return processed_market
|
|
419
521
|
|
|
@@ -523,12 +625,13 @@ class DeployableTraderAgent(DeployablePredictionAgent):
|
|
|
523
625
|
super().initialize_langfuse()
|
|
524
626
|
# Auto-observe all the methods where it makes sense, so that subclassses don't need to do it manually.
|
|
525
627
|
self.build_trades = observe()(self.build_trades) # type: ignore[method-assign]
|
|
628
|
+
self.execute_trades = observe()(self.execute_trades) # type: ignore[method-assign]
|
|
526
629
|
|
|
527
630
|
def check_min_required_balance_to_trade(self, market: AgentMarket) -> None:
|
|
528
631
|
api_keys = APIKeys()
|
|
529
632
|
|
|
530
633
|
# Get the strategy to know how much it will bet.
|
|
531
|
-
strategy = self.
|
|
634
|
+
strategy = self.get_betting_strategy_supported(market)
|
|
532
635
|
# Have a little bandwidth after the bet.
|
|
533
636
|
min_required_balance_to_trade = strategy.maximum_possible_bet_amount * 1.01
|
|
534
637
|
|
|
@@ -543,8 +646,10 @@ class DeployableTraderAgent(DeployablePredictionAgent):
|
|
|
543
646
|
|
|
544
647
|
total_amount = market.get_in_usd(market.get_tiny_bet_amount())
|
|
545
648
|
existing_position = market.get_position(user_id=user_id)
|
|
649
|
+
|
|
546
650
|
if existing_position and existing_position.total_amount_current > USD(0):
|
|
547
651
|
total_amount += existing_position.total_amount_current
|
|
652
|
+
|
|
548
653
|
return total_amount
|
|
549
654
|
|
|
550
655
|
def get_betting_strategy(self, market: AgentMarket) -> BettingStrategy:
|
|
@@ -554,7 +659,18 @@ class DeployableTraderAgent(DeployablePredictionAgent):
|
|
|
554
659
|
Given the market and prediction, agent uses this method to calculate optimal outcome and bet size.
|
|
555
660
|
"""
|
|
556
661
|
total_amount = self.get_total_amount_to_bet(market)
|
|
557
|
-
return
|
|
662
|
+
return CategoricalMaxAccuracyBettingStrategy(max_position_amount=total_amount)
|
|
663
|
+
|
|
664
|
+
def get_betting_strategy_supported(self, market: AgentMarket) -> BettingStrategy:
|
|
665
|
+
"""
|
|
666
|
+
Use this class internally to assert that the configured betting strategy works with the given market.
|
|
667
|
+
"""
|
|
668
|
+
strategy = self.get_betting_strategy(market=market)
|
|
669
|
+
if not strategy.is_market_supported(market):
|
|
670
|
+
raise ValueError(
|
|
671
|
+
f"Market {market.url} is not supported by the strategy {strategy}."
|
|
672
|
+
)
|
|
673
|
+
return strategy
|
|
558
674
|
|
|
559
675
|
def build_trades(
|
|
560
676
|
self,
|
|
@@ -562,36 +678,13 @@ class DeployableTraderAgent(DeployablePredictionAgent):
|
|
|
562
678
|
answer: CategoricalProbabilisticAnswer,
|
|
563
679
|
existing_position: ExistingPosition | None,
|
|
564
680
|
) -> list[Trade]:
|
|
565
|
-
strategy = self.
|
|
681
|
+
strategy = self.get_betting_strategy_supported(market=market)
|
|
566
682
|
trades = strategy.calculate_trades(existing_position, answer, market)
|
|
567
683
|
return trades
|
|
568
684
|
|
|
569
|
-
def
|
|
570
|
-
self,
|
|
571
|
-
) ->
|
|
572
|
-
super().before_process_market(market_type, market)
|
|
573
|
-
self.check_min_required_balance_to_trade(market)
|
|
574
|
-
|
|
575
|
-
def process_market(
|
|
576
|
-
self,
|
|
577
|
-
market_type: MarketType,
|
|
578
|
-
market: AgentMarket,
|
|
579
|
-
verify_market: bool = True,
|
|
580
|
-
) -> ProcessedTradedMarket | None:
|
|
581
|
-
processed_market = super().process_market(market_type, market, verify_market)
|
|
582
|
-
if processed_market is None:
|
|
583
|
-
return None
|
|
584
|
-
|
|
585
|
-
api_keys = APIKeys()
|
|
586
|
-
user_id = market.get_user_id(api_keys=api_keys)
|
|
587
|
-
|
|
588
|
-
existing_position = market.get_position(user_id=user_id)
|
|
589
|
-
trades = self.build_trades(
|
|
590
|
-
market=market,
|
|
591
|
-
answer=processed_market.answer,
|
|
592
|
-
existing_position=existing_position,
|
|
593
|
-
)
|
|
594
|
-
|
|
685
|
+
def execute_trades(
|
|
686
|
+
self, market: AgentMarket, trades: list[Trade]
|
|
687
|
+
) -> list[PlacedTrade]:
|
|
595
688
|
# It can take quite some time before agent processes all the markets, recheck here if the market didn't get closed in the meantime, to not error out completely.
|
|
596
689
|
# Unfortunately, we can not just add some room into closing time of the market while fetching them, because liquidity can be removed at any time by the liquidity providers.
|
|
597
690
|
still_tradeable = market.can_be_traded()
|
|
@@ -600,7 +693,8 @@ class DeployableTraderAgent(DeployablePredictionAgent):
|
|
|
600
693
|
f"Market {market.question=} ({market.url}) was selected to processing, but is not tradeable anymore."
|
|
601
694
|
)
|
|
602
695
|
|
|
603
|
-
placed_trades = []
|
|
696
|
+
placed_trades: list[PlacedTrade] = []
|
|
697
|
+
|
|
604
698
|
for trade in trades:
|
|
605
699
|
logger.info(f"Executing trade {trade} on market {market.id} ({market.url})")
|
|
606
700
|
|
|
@@ -612,8 +706,13 @@ class DeployableTraderAgent(DeployablePredictionAgent):
|
|
|
612
706
|
)
|
|
613
707
|
case TradeType.SELL:
|
|
614
708
|
# Get actual value of the position we are going to sell, and if it's less than we wanted to sell, simply sell all of it.
|
|
709
|
+
# In this palce, we expect to have positions, so retry a few times if None are returned, which sometimes happens due to flaky subgraph.
|
|
615
710
|
current_position = check_not_none(
|
|
616
|
-
|
|
711
|
+
retry_until_true(
|
|
712
|
+
lambda x: x is not None and x.total_amount_ot > 0
|
|
713
|
+
)(market.get_position)(
|
|
714
|
+
market.get_user_id(api_keys=self.api_keys)
|
|
715
|
+
),
|
|
617
716
|
"Should exists if we are going to sell outcomes.",
|
|
618
717
|
)
|
|
619
718
|
|
|
@@ -641,7 +740,40 @@ class DeployableTraderAgent(DeployablePredictionAgent):
|
|
|
641
740
|
f"Trade execution skipped because, {self.place_trades=} or {still_tradeable=}."
|
|
642
741
|
)
|
|
643
742
|
|
|
644
|
-
|
|
743
|
+
return placed_trades
|
|
744
|
+
|
|
745
|
+
def before_process_market(
|
|
746
|
+
self, market_type: MarketType, market: AgentMarket
|
|
747
|
+
) -> None:
|
|
748
|
+
super().before_process_market(market_type, market)
|
|
749
|
+
self.check_min_required_balance_to_trade(market)
|
|
750
|
+
|
|
751
|
+
def process_market(
|
|
752
|
+
self,
|
|
753
|
+
market_type: MarketType,
|
|
754
|
+
market: AgentMarket,
|
|
755
|
+
verify_market: bool = True,
|
|
756
|
+
) -> ProcessedMarket | None:
|
|
757
|
+
processed_market = super().process_market(market_type, market, verify_market)
|
|
758
|
+
if processed_market is None:
|
|
759
|
+
return None
|
|
760
|
+
|
|
761
|
+
user_id = market.get_user_id(api_keys=self.api_keys)
|
|
762
|
+
|
|
763
|
+
try:
|
|
764
|
+
existing_position = market.get_position(user_id=user_id)
|
|
765
|
+
except Exception as e:
|
|
766
|
+
logger.warning(f"Could not get position for user {user_id}, exception {e}")
|
|
767
|
+
return None
|
|
768
|
+
|
|
769
|
+
trades = self.build_trades(
|
|
770
|
+
market=market,
|
|
771
|
+
answer=processed_market.answer,
|
|
772
|
+
existing_position=existing_position,
|
|
773
|
+
)
|
|
774
|
+
placed_trades = self.execute_trades(market, trades)
|
|
775
|
+
|
|
776
|
+
traded_market = ProcessedMarket(
|
|
645
777
|
answer=processed_market.answer, trades=placed_trades
|
|
646
778
|
)
|
|
647
779
|
logger.info(f"Traded market {market.question=} from {market.url=}.")
|
|
@@ -659,10 +791,10 @@ class DeployableTraderAgent(DeployablePredictionAgent):
|
|
|
659
791
|
market,
|
|
660
792
|
processed_market,
|
|
661
793
|
)
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
794
|
+
|
|
795
|
+
if self.store_trades and processed_market is not None:
|
|
796
|
+
market.store_trades(processed_market, api_keys, self.agent_name)
|
|
797
|
+
else:
|
|
798
|
+
logger.info(
|
|
799
|
+
f"Trades {processed_market=} not stored because {self.store_trades=}."
|
|
800
|
+
)
|
|
@@ -6,7 +6,7 @@ from prediction_market_agent_tooling.markets.agent_market import AgentMarket, So
|
|
|
6
6
|
from prediction_market_agent_tooling.markets.data_models import (
|
|
7
7
|
CategoricalProbabilisticAnswer,
|
|
8
8
|
)
|
|
9
|
-
from prediction_market_agent_tooling.markets.
|
|
9
|
+
from prediction_market_agent_tooling.markets.market_type import MarketType
|
|
10
10
|
|
|
11
11
|
|
|
12
12
|
class DeployableCoinFlipAgent(DeployableTraderAgent):
|