prediction-market-agent-tooling 0.64.12.dev660__py3-none-any.whl → 0.65.1__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/benchmark/agents.py +19 -16
- prediction_market_agent_tooling/benchmark/benchmark.py +94 -84
- prediction_market_agent_tooling/benchmark/utils.py +8 -9
- prediction_market_agent_tooling/config.py +28 -7
- prediction_market_agent_tooling/deploy/agent.py +85 -125
- prediction_market_agent_tooling/deploy/agent_example.py +20 -10
- prediction_market_agent_tooling/deploy/betting_strategy.py +222 -96
- prediction_market_agent_tooling/deploy/constants.py +4 -0
- prediction_market_agent_tooling/jobs/jobs_models.py +15 -4
- prediction_market_agent_tooling/jobs/omen/omen_jobs.py +3 -3
- prediction_market_agent_tooling/loggers.py +1 -0
- prediction_market_agent_tooling/markets/agent_market.py +145 -50
- prediction_market_agent_tooling/markets/blockchain_utils.py +10 -1
- prediction_market_agent_tooling/markets/data_models.py +83 -17
- prediction_market_agent_tooling/markets/manifold/api.py +18 -7
- prediction_market_agent_tooling/markets/manifold/data_models.py +23 -16
- prediction_market_agent_tooling/markets/manifold/manifold.py +18 -18
- prediction_market_agent_tooling/markets/manifold/utils.py +7 -12
- prediction_market_agent_tooling/markets/markets.py +2 -1
- prediction_market_agent_tooling/markets/metaculus/metaculus.py +29 -4
- prediction_market_agent_tooling/markets/omen/data_models.py +17 -32
- prediction_market_agent_tooling/markets/omen/omen.py +65 -108
- prediction_market_agent_tooling/markets/omen/omen_contracts.py +2 -5
- prediction_market_agent_tooling/markets/omen/omen_resolving.py +13 -13
- prediction_market_agent_tooling/markets/omen/omen_subgraph_handler.py +18 -12
- prediction_market_agent_tooling/markets/polymarket/data_models.py +7 -3
- prediction_market_agent_tooling/markets/polymarket/data_models_web.py +7 -3
- prediction_market_agent_tooling/markets/polymarket/polymarket.py +5 -4
- prediction_market_agent_tooling/markets/seer/data_models.py +0 -83
- prediction_market_agent_tooling/markets/seer/price_manager.py +44 -30
- prediction_market_agent_tooling/markets/seer/seer.py +105 -105
- prediction_market_agent_tooling/markets/seer/seer_subgraph_handler.py +34 -41
- prediction_market_agent_tooling/tools/betting_strategies/kelly_criterion.py +1 -1
- prediction_market_agent_tooling/tools/cow/cow_order.py +10 -3
- prediction_market_agent_tooling/tools/is_predictable.py +2 -3
- prediction_market_agent_tooling/tools/langfuse_client_utils.py +4 -4
- prediction_market_agent_tooling/tools/omen/sell_positions.py +3 -2
- prediction_market_agent_tooling/tools/utils.py +26 -13
- {prediction_market_agent_tooling-0.64.12.dev660.dist-info → prediction_market_agent_tooling-0.65.1.dist-info}/METADATA +2 -2
- {prediction_market_agent_tooling-0.64.12.dev660.dist-info → prediction_market_agent_tooling-0.65.1.dist-info}/RECORD +43 -53
- prediction_market_agent_tooling/monitor/financial_metrics/financial_metrics.py +0 -68
- prediction_market_agent_tooling/monitor/markets/manifold.py +0 -90
- prediction_market_agent_tooling/monitor/markets/metaculus.py +0 -43
- prediction_market_agent_tooling/monitor/markets/omen.py +0 -88
- prediction_market_agent_tooling/monitor/markets/polymarket.py +0 -49
- prediction_market_agent_tooling/monitor/monitor.py +0 -406
- prediction_market_agent_tooling/monitor/monitor_app.py +0 -149
- prediction_market_agent_tooling/monitor/monitor_settings.py +0 -27
- prediction_market_agent_tooling/tools/betting_strategies/market_moving.py +0 -146
- prediction_market_agent_tooling/tools/betting_strategies/minimum_bet_to_win.py +0 -12
- {prediction_market_agent_tooling-0.64.12.dev660.dist-info → prediction_market_agent_tooling-0.65.1.dist-info}/LICENSE +0 -0
- {prediction_market_agent_tooling-0.64.12.dev660.dist-info → prediction_market_agent_tooling-0.65.1.dist-info}/WHEEL +0 -0
- {prediction_market_agent_tooling-0.64.12.dev660.dist-info → prediction_market_agent_tooling-0.65.1.dist-info}/entry_points.txt +0 -0
@@ -1,7 +1,4 @@
|
|
1
1
|
import getpass
|
2
|
-
import inspect
|
3
|
-
import os
|
4
|
-
import tempfile
|
5
2
|
import time
|
6
3
|
import typing as t
|
7
4
|
from datetime import timedelta
|
@@ -13,19 +10,9 @@ from pydantic import computed_field
|
|
13
10
|
from prediction_market_agent_tooling.config import APIKeys
|
14
11
|
from prediction_market_agent_tooling.deploy.betting_strategy import (
|
15
12
|
BettingStrategy,
|
16
|
-
|
13
|
+
MultiCategoricalMaxAccuracyBettingStrategy,
|
17
14
|
TradeType,
|
18
15
|
)
|
19
|
-
from prediction_market_agent_tooling.deploy.constants import (
|
20
|
-
MARKET_TYPE_KEY,
|
21
|
-
REPOSITORY_KEY,
|
22
|
-
)
|
23
|
-
from prediction_market_agent_tooling.deploy.gcp.deploy import (
|
24
|
-
deploy_to_gcp,
|
25
|
-
run_deployed_gcp_function,
|
26
|
-
schedule_deployed_gcp_function,
|
27
|
-
)
|
28
|
-
from prediction_market_agent_tooling.deploy.gcp.utils import gcp_function_is_active
|
29
16
|
from prediction_market_agent_tooling.deploy.trade_interval import (
|
30
17
|
FixedInterval,
|
31
18
|
TradeInterval,
|
@@ -40,6 +27,7 @@ from prediction_market_agent_tooling.markets.agent_market import (
|
|
40
27
|
SortBy,
|
41
28
|
)
|
42
29
|
from prediction_market_agent_tooling.markets.data_models import (
|
30
|
+
CategoricalProbabilisticAnswer,
|
43
31
|
ExistingPosition,
|
44
32
|
PlacedTrade,
|
45
33
|
ProbabilisticAnswer,
|
@@ -52,9 +40,6 @@ from prediction_market_agent_tooling.markets.markets import (
|
|
52
40
|
from prediction_market_agent_tooling.markets.omen.omen import (
|
53
41
|
send_keeping_token_to_eoa_xdai,
|
54
42
|
)
|
55
|
-
from prediction_market_agent_tooling.monitor.monitor_app import (
|
56
|
-
MARKET_TYPE_TO_DEPLOYED_AGENT,
|
57
|
-
)
|
58
43
|
from prediction_market_agent_tooling.tools.custom_exceptions import (
|
59
44
|
CantPayForGasError,
|
60
45
|
OutOfFundsError,
|
@@ -185,88 +170,6 @@ class DeployableAgent:
|
|
185
170
|
self.run(market_type=market_type)
|
186
171
|
time.sleep(sleep_time)
|
187
172
|
|
188
|
-
def deploy_gcp(
|
189
|
-
self,
|
190
|
-
repository: str,
|
191
|
-
market_type: MarketType,
|
192
|
-
api_keys: APIKeys,
|
193
|
-
memory: int,
|
194
|
-
labels: dict[str, str] | None = None,
|
195
|
-
env_vars: dict[str, str] | None = None,
|
196
|
-
secrets: dict[str, str] | None = None,
|
197
|
-
cron_schedule: str | None = None,
|
198
|
-
gcp_fname: str | None = None,
|
199
|
-
start_time: DatetimeUTC | None = None,
|
200
|
-
timeout: int = 180,
|
201
|
-
) -> None:
|
202
|
-
"""
|
203
|
-
Deploy the agent as GCP Function.
|
204
|
-
"""
|
205
|
-
path_to_agent_file = os.path.relpath(inspect.getfile(self.__class__))
|
206
|
-
|
207
|
-
entrypoint_function_name = "main"
|
208
|
-
entrypoint_template = f"""
|
209
|
-
from {path_to_agent_file.replace("/", ".").replace(".py", "")} import *
|
210
|
-
import functions_framework
|
211
|
-
from prediction_market_agent_tooling.markets.markets import MarketType
|
212
|
-
|
213
|
-
@functions_framework.http
|
214
|
-
def {entrypoint_function_name}(request) -> str:
|
215
|
-
{self.__class__.__name__}().run(market_type={market_type.__class__.__name__}.{market_type.name})
|
216
|
-
return "Success"
|
217
|
-
"""
|
218
|
-
|
219
|
-
gcp_fname = gcp_fname or self.get_gcloud_fname(market_type)
|
220
|
-
|
221
|
-
# For labels, only hyphens (-), underscores (_), lowercase characters, and numbers are allowed in values.
|
222
|
-
labels = (labels or {}) | {
|
223
|
-
MARKET_TYPE_KEY: market_type.value,
|
224
|
-
}
|
225
|
-
env_vars = (env_vars or {}) | {
|
226
|
-
REPOSITORY_KEY: repository,
|
227
|
-
}
|
228
|
-
secrets = secrets or {}
|
229
|
-
|
230
|
-
env_vars |= api_keys.model_dump_public()
|
231
|
-
secrets |= api_keys.model_dump_secrets()
|
232
|
-
|
233
|
-
monitor_agent = MARKET_TYPE_TO_DEPLOYED_AGENT[market_type].from_api_keys(
|
234
|
-
name=gcp_fname,
|
235
|
-
start_time=start_time or utcnow(),
|
236
|
-
api_keys=api_keys,
|
237
|
-
)
|
238
|
-
env_vars |= monitor_agent.model_dump_prefixed()
|
239
|
-
|
240
|
-
with tempfile.NamedTemporaryFile(mode="w", suffix=".py") as f:
|
241
|
-
f.write(entrypoint_template)
|
242
|
-
f.flush()
|
243
|
-
|
244
|
-
fname = deploy_to_gcp(
|
245
|
-
gcp_fname=gcp_fname,
|
246
|
-
requirements_file=None,
|
247
|
-
extra_deps=[repository],
|
248
|
-
function_file=f.name,
|
249
|
-
labels=labels,
|
250
|
-
env_vars=env_vars,
|
251
|
-
secrets=secrets,
|
252
|
-
memory=memory,
|
253
|
-
entrypoint_function_name=entrypoint_function_name,
|
254
|
-
timeout=timeout,
|
255
|
-
)
|
256
|
-
|
257
|
-
# Check that the function is deployed
|
258
|
-
if not gcp_function_is_active(fname):
|
259
|
-
raise RuntimeError("Failed to deploy the function")
|
260
|
-
|
261
|
-
# Run the function
|
262
|
-
response = run_deployed_gcp_function(fname)
|
263
|
-
if not response.ok:
|
264
|
-
raise RuntimeError("Failed to run the deployed function")
|
265
|
-
|
266
|
-
# Schedule the function
|
267
|
-
if cron_schedule:
|
268
|
-
schedule_deployed_gcp_function(fname, cron_schedule=cron_schedule)
|
269
|
-
|
270
173
|
def run(self, market_type: MarketType) -> None:
|
271
174
|
"""
|
272
175
|
Run single iteration of the agent.
|
@@ -292,6 +195,7 @@ class DeployablePredictionAgent(DeployableAgent):
|
|
292
195
|
n_markets_to_fetch: int = MAX_AVAILABLE_MARKETS
|
293
196
|
trade_on_markets_created_after: DatetimeUTC | None = None
|
294
197
|
get_markets_sort_by: SortBy = SortBy.CLOSING_SOONEST
|
198
|
+
get_markets_filter_by: FilterBy = FilterBy.OPEN
|
295
199
|
|
296
200
|
# Agent behaviour when filtering fetched markets
|
297
201
|
allow_invalid_questions: bool = False
|
@@ -377,9 +281,13 @@ class DeployablePredictionAgent(DeployableAgent):
|
|
377
281
|
return False
|
378
282
|
|
379
283
|
# Manifold allows to bet only on markets with probability between 1 and 99.
|
380
|
-
if market_type == MarketType.MANIFOLD
|
381
|
-
|
382
|
-
|
284
|
+
if market_type == MarketType.MANIFOLD:
|
285
|
+
probability_yes = market.probabilities[
|
286
|
+
market.get_outcome_str_from_bool(True)
|
287
|
+
]
|
288
|
+
if not probability_yes or not 1 < probability_yes < 99:
|
289
|
+
logger.info("Manifold's market probability not in the range 1-99.")
|
290
|
+
return False
|
383
291
|
|
384
292
|
# Do as a last check, as it uses paid OpenAI API.
|
385
293
|
if not is_predictable_binary(market.question):
|
@@ -392,11 +300,32 @@ class DeployablePredictionAgent(DeployableAgent):
|
|
392
300
|
|
393
301
|
return True
|
394
302
|
|
303
|
+
def answer_categorical_market(
|
304
|
+
self, market: AgentMarket
|
305
|
+
) -> CategoricalProbabilisticAnswer | None:
|
306
|
+
raise NotImplementedError("This method must be implemented by the subclass")
|
307
|
+
|
395
308
|
def answer_binary_market(self, market: AgentMarket) -> ProbabilisticAnswer | None:
|
396
309
|
"""
|
397
|
-
Answer the binary market.
|
310
|
+
Answer the binary market.
|
311
|
+
|
312
|
+
If this method is not overridden by the subclass, it will fall back to using
|
313
|
+
answer_categorical_market(). Therefore, subclasses only need to implement
|
314
|
+
answer_categorical_market() if they want to handle both types of markets.
|
398
315
|
"""
|
399
|
-
raise NotImplementedError(
|
316
|
+
raise NotImplementedError(
|
317
|
+
"Either this method, or answer_categorical_market, must be implemented by the subclass."
|
318
|
+
)
|
319
|
+
|
320
|
+
@property
|
321
|
+
def fetch_categorical_markets(self) -> bool:
|
322
|
+
# Check if the subclass has implemented the answer_categorical_market method, if yes, fetch categorical markets as well.
|
323
|
+
if (
|
324
|
+
self.answer_categorical_market.__func__ # type: ignore[attr-defined] # This works just fine, but mypy doesn't know about it for some reason.
|
325
|
+
is not DeployablePredictionAgent.answer_categorical_market
|
326
|
+
):
|
327
|
+
return True
|
328
|
+
return False
|
400
329
|
|
401
330
|
def get_markets(
|
402
331
|
self,
|
@@ -407,11 +336,12 @@ class DeployablePredictionAgent(DeployableAgent):
|
|
407
336
|
"""
|
408
337
|
cls = market_type.market_class
|
409
338
|
# Fetch the soonest closing markets to choose from
|
410
|
-
available_markets = cls.
|
339
|
+
available_markets = cls.get_markets(
|
411
340
|
limit=self.n_markets_to_fetch,
|
412
341
|
sort_by=self.get_markets_sort_by,
|
413
|
-
filter_by=
|
342
|
+
filter_by=self.get_markets_filter_by,
|
414
343
|
created_after=self.trade_on_markets_created_after,
|
344
|
+
fetch_categorical_markets=self.fetch_categorical_markets,
|
415
345
|
)
|
416
346
|
return available_markets
|
417
347
|
|
@@ -432,6 +362,35 @@ class DeployablePredictionAgent(DeployableAgent):
|
|
432
362
|
multiplier=3,
|
433
363
|
)
|
434
364
|
|
365
|
+
def build_answer(
|
366
|
+
self,
|
367
|
+
market_type: MarketType,
|
368
|
+
market: AgentMarket,
|
369
|
+
verify_market: bool = True,
|
370
|
+
) -> CategoricalProbabilisticAnswer | None:
|
371
|
+
if verify_market and not self.verify_market(market_type, market):
|
372
|
+
logger.info(f"Market '{market.question}' doesn't meet the criteria.")
|
373
|
+
return None
|
374
|
+
|
375
|
+
logger.info(f"Answering market '{market.question}'.")
|
376
|
+
|
377
|
+
if market.is_binary:
|
378
|
+
try:
|
379
|
+
binary_answer = self.answer_binary_market(market)
|
380
|
+
return (
|
381
|
+
CategoricalProbabilisticAnswer.from_probabilistic_answer(
|
382
|
+
binary_answer
|
383
|
+
)
|
384
|
+
if binary_answer is not None
|
385
|
+
else None
|
386
|
+
)
|
387
|
+
except NotImplementedError:
|
388
|
+
logger.info(
|
389
|
+
"answer_binary_market() not implemented, falling back to answer_categorical_market()"
|
390
|
+
)
|
391
|
+
|
392
|
+
return self.answer_categorical_market(market)
|
393
|
+
|
435
394
|
def process_market(
|
436
395
|
self,
|
437
396
|
market_type: MarketType,
|
@@ -443,13 +402,9 @@ class DeployablePredictionAgent(DeployableAgent):
|
|
443
402
|
f"Processing market {market.question=} from {market.url=} with liquidity {market.get_liquidity()}."
|
444
403
|
)
|
445
404
|
|
446
|
-
answer
|
447
|
-
|
448
|
-
|
449
|
-
answer = None
|
450
|
-
else:
|
451
|
-
logger.info(f"Answering market '{market.question}'.")
|
452
|
-
answer = self.answer_binary_market(market)
|
405
|
+
answer = self.build_answer(
|
406
|
+
market=market, market_type=market_type, verify_market=verify_market
|
407
|
+
)
|
453
408
|
|
454
409
|
processed_market = (
|
455
410
|
ProcessedMarket(answer=answer) if answer is not None else None
|
@@ -494,6 +449,7 @@ class DeployablePredictionAgent(DeployableAgent):
|
|
494
449
|
"""
|
495
450
|
logger.info("Start processing of markets.")
|
496
451
|
available_markets = self.get_markets(market_type)
|
452
|
+
|
497
453
|
logger.info(
|
498
454
|
f"Fetched {len(available_markets)=} markets to process, going to process {self.bet_on_n_markets_per_run=}."
|
499
455
|
)
|
@@ -580,24 +536,29 @@ class DeployableTraderAgent(DeployablePredictionAgent):
|
|
580
536
|
f"Minimum required balance {min_required_balance_to_trade} for agent {api_keys.bet_from_address=} is not met."
|
581
537
|
)
|
582
538
|
|
539
|
+
@staticmethod
|
540
|
+
def get_total_amount_to_bet(market: AgentMarket) -> USD:
|
541
|
+
user_id = market.get_user_id(api_keys=APIKeys())
|
542
|
+
|
543
|
+
total_amount = market.get_in_usd(market.get_tiny_bet_amount())
|
544
|
+
existing_position = market.get_position(user_id=user_id)
|
545
|
+
if existing_position and existing_position.total_amount_current > USD(0):
|
546
|
+
total_amount += existing_position.total_amount_current
|
547
|
+
return total_amount
|
548
|
+
|
583
549
|
def get_betting_strategy(self, market: AgentMarket) -> BettingStrategy:
|
584
550
|
"""
|
585
551
|
Override this method to customize betting strategy of your agent.
|
586
552
|
|
587
553
|
Given the market and prediction, agent uses this method to calculate optimal outcome and bet size.
|
588
554
|
"""
|
589
|
-
|
590
|
-
|
591
|
-
total_amount = market.get_in_usd(market.get_tiny_bet_amount())
|
592
|
-
if existing_position := market.get_position(user_id=user_id):
|
593
|
-
total_amount += existing_position.total_amount_current
|
594
|
-
|
595
|
-
return MaxAccuracyBettingStrategy(bet_amount=total_amount)
|
555
|
+
total_amount = self.get_total_amount_to_bet(market)
|
556
|
+
return MultiCategoricalMaxAccuracyBettingStrategy(bet_amount=total_amount)
|
596
557
|
|
597
558
|
def build_trades(
|
598
559
|
self,
|
599
560
|
market: AgentMarket,
|
600
|
-
answer:
|
561
|
+
answer: CategoricalProbabilisticAnswer,
|
601
562
|
existing_position: ExistingPosition | None,
|
602
563
|
) -> list[Trade]:
|
603
564
|
strategy = self.get_betting_strategy(market=market)
|
@@ -654,8 +615,9 @@ class DeployableTraderAgent(DeployablePredictionAgent):
|
|
654
615
|
market.get_position(user_id),
|
655
616
|
"Should exists if we are going to sell outcomes.",
|
656
617
|
)
|
618
|
+
|
657
619
|
current_position_value_usd = current_position.amounts_current[
|
658
|
-
|
620
|
+
trade.outcome
|
659
621
|
]
|
660
622
|
amount_to_sell: USD | OutcomeToken
|
661
623
|
if current_position_value_usd <= trade.amount:
|
@@ -663,9 +625,7 @@ class DeployableTraderAgent(DeployablePredictionAgent):
|
|
663
625
|
f"Current value of position {trade.outcome=}, {current_position_value_usd=} is less than the desired selling amount {trade.amount=}. Selling all."
|
664
626
|
)
|
665
627
|
# In case the agent asked to sell too much, provide the amount to sell as all outcome tokens, instead of in USD, to minimze fx fluctuations when selling.
|
666
|
-
amount_to_sell = current_position.amounts_ot[
|
667
|
-
market.get_outcome_str_from_bool(trade.outcome)
|
668
|
-
]
|
628
|
+
amount_to_sell = current_position.amounts_ot[trade.outcome]
|
669
629
|
else:
|
670
630
|
amount_to_sell = trade.amount
|
671
631
|
id = market.sell_tokens(
|
@@ -1,27 +1,37 @@
|
|
1
1
|
import random
|
2
2
|
|
3
|
-
from prediction_market_agent_tooling.deploy.agent import
|
4
|
-
DeployableTraderAgent,
|
5
|
-
ProbabilisticAnswer,
|
6
|
-
)
|
3
|
+
from prediction_market_agent_tooling.deploy.agent import DeployableTraderAgent
|
7
4
|
from prediction_market_agent_tooling.gtypes import Probability
|
8
|
-
from prediction_market_agent_tooling.markets.agent_market import AgentMarket
|
5
|
+
from prediction_market_agent_tooling.markets.agent_market import AgentMarket, SortBy
|
6
|
+
from prediction_market_agent_tooling.markets.data_models import (
|
7
|
+
CategoricalProbabilisticAnswer,
|
8
|
+
)
|
9
9
|
from prediction_market_agent_tooling.markets.markets import MarketType
|
10
10
|
|
11
11
|
|
12
12
|
class DeployableCoinFlipAgent(DeployableTraderAgent):
|
13
|
+
get_markets_sort_by = SortBy.HIGHEST_LIQUIDITY
|
14
|
+
|
13
15
|
def verify_market(self, market_type: MarketType, market: AgentMarket) -> bool:
|
14
16
|
return True
|
15
17
|
|
16
|
-
def
|
17
|
-
|
18
|
-
|
19
|
-
|
18
|
+
def answer_categorical_market(
|
19
|
+
self, market: AgentMarket
|
20
|
+
) -> CategoricalProbabilisticAnswer:
|
21
|
+
decision = random.choice(market.outcomes)
|
22
|
+
probabilities = {decision: Probability(1.0)}
|
23
|
+
for outcome in market.outcomes:
|
24
|
+
if outcome != decision:
|
25
|
+
probabilities[outcome] = Probability(0.0)
|
26
|
+
return CategoricalProbabilisticAnswer(
|
27
|
+
probabilities=probabilities,
|
20
28
|
confidence=0.5,
|
21
29
|
reasoning="I flipped a coin to decide.",
|
22
30
|
)
|
23
31
|
|
24
32
|
|
25
33
|
class DeployableAlwaysRaiseAgent(DeployableTraderAgent):
|
26
|
-
def
|
34
|
+
def answer_categorical_market(
|
35
|
+
self, market: AgentMarket
|
36
|
+
) -> CategoricalProbabilisticAnswer | None:
|
27
37
|
raise RuntimeError("I always raise!")
|