prediction-market-agent-tooling 0.64.12.dev660__py3-none-any.whl → 0.65.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- prediction_market_agent_tooling/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/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/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.0.dist-info}/METADATA +2 -2
- {prediction_market_agent_tooling-0.64.12.dev660.dist-info → prediction_market_agent_tooling-0.65.0.dist-info}/RECORD +41 -51
- 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.0.dist-info}/LICENSE +0 -0
- {prediction_market_agent_tooling-0.64.12.dev660.dist-info → prediction_market_agent_tooling-0.65.0.dist-info}/WHEEL +0 -0
- {prediction_market_agent_tooling-0.64.12.dev660.dist-info → prediction_market_agent_tooling-0.65.0.dist-info}/entry_points.txt +0 -0
@@ -1,11 +1,10 @@
|
|
1
1
|
import typing as t
|
2
|
-
from math import ceil
|
3
2
|
|
4
3
|
from prediction_market_agent_tooling.config import APIKeys
|
5
4
|
from prediction_market_agent_tooling.gtypes import (
|
6
5
|
USD,
|
7
6
|
CollateralToken,
|
8
|
-
|
7
|
+
OutcomeStr,
|
9
8
|
Probability,
|
10
9
|
)
|
11
10
|
from prediction_market_agent_tooling.markets.agent_market import (
|
@@ -25,9 +24,6 @@ from prediction_market_agent_tooling.markets.manifold.data_models import (
|
|
25
24
|
FullManifoldMarket,
|
26
25
|
usd_to_mana,
|
27
26
|
)
|
28
|
-
from prediction_market_agent_tooling.tools.betting_strategies.minimum_bet_to_win import (
|
29
|
-
minimum_bet_to_win,
|
30
|
-
)
|
31
27
|
from prediction_market_agent_tooling.tools.utils import DatetimeUTC
|
32
28
|
|
33
29
|
|
@@ -45,22 +41,17 @@ class ManifoldAgentMarket(AgentMarket):
|
|
45
41
|
absolute=0.25, # For doing trades via API.
|
46
42
|
)
|
47
43
|
|
44
|
+
# We restrict Manifold to binary markets, hence current_p_yes always defined.
|
45
|
+
current_p_yes: Probability
|
46
|
+
|
48
47
|
def get_last_trade_p_yes(self) -> Probability:
|
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_yes
|
51
50
|
|
52
|
-
def get_last_trade_p_no(self) -> Probability:
|
53
|
-
"""On Manifold, probablities aren't updated after the closure, so we can just use the current probability"""
|
54
|
-
return self.current_p_no
|
55
|
-
|
56
51
|
def get_tiny_bet_amount(self) -> CollateralToken:
|
57
52
|
return CollateralToken(1)
|
58
53
|
|
59
|
-
def
|
60
|
-
# Manifold lowest bet is 1 Mana, so we need to ceil the result.
|
61
|
-
return Mana(ceil(minimum_bet_to_win(answer, amount_to_win, self)))
|
62
|
-
|
63
|
-
def place_bet(self, outcome: bool, amount: USD) -> str:
|
54
|
+
def place_bet(self, outcome: OutcomeStr, amount: USD) -> str:
|
64
55
|
self.get_usd_in_token(amount)
|
65
56
|
bet = place_bet(
|
66
57
|
amount=usd_to_mana(amount),
|
@@ -72,6 +63,15 @@ class ManifoldAgentMarket(AgentMarket):
|
|
72
63
|
|
73
64
|
@staticmethod
|
74
65
|
def from_data_model(model: FullManifoldMarket) -> "ManifoldAgentMarket":
|
66
|
+
outcome_token_pool = {o: model.pool.size_for_outcome(o) for o in model.outcomes}
|
67
|
+
|
68
|
+
prob_map = AgentMarket.build_probability_map(
|
69
|
+
outcomes=list(outcome_token_pool.keys()),
|
70
|
+
outcome_token_amounts=list(
|
71
|
+
[i.as_outcome_wei for i in outcome_token_pool.values()]
|
72
|
+
),
|
73
|
+
)
|
74
|
+
|
75
75
|
return ManifoldAgentMarket(
|
76
76
|
id=model.id,
|
77
77
|
question=model.question,
|
@@ -83,18 +83,18 @@ class ManifoldAgentMarket(AgentMarket):
|
|
83
83
|
current_p_yes=model.probability,
|
84
84
|
url=model.url,
|
85
85
|
volume=model.volume,
|
86
|
-
outcome_token_pool=
|
87
|
-
|
88
|
-
},
|
86
|
+
outcome_token_pool=outcome_token_pool,
|
87
|
+
probabilities=prob_map,
|
89
88
|
)
|
90
89
|
|
91
90
|
@staticmethod
|
92
|
-
def
|
91
|
+
def get_markets(
|
93
92
|
limit: int,
|
94
93
|
sort_by: SortBy,
|
95
94
|
filter_by: FilterBy = FilterBy.OPEN,
|
96
95
|
created_after: t.Optional[DatetimeUTC] = None,
|
97
96
|
excluded_questions: set[str] | None = None,
|
97
|
+
fetch_categorical_markets: bool = False,
|
98
98
|
) -> t.Sequence["ManifoldAgentMarket"]:
|
99
99
|
sort: t.Literal["newest", "close-date"] | None
|
100
100
|
if sort_by == SortBy.CLOSING_SOONEST:
|
@@ -1,15 +1,10 @@
|
|
1
|
+
import typing as t
|
2
|
+
|
3
|
+
from prediction_market_agent_tooling.gtypes import OutcomeStr
|
1
4
|
from prediction_market_agent_tooling.markets.data_models import Resolution
|
2
|
-
from prediction_market_agent_tooling.markets.manifold.api import (
|
3
|
-
get_manifold_binary_markets,
|
4
|
-
)
|
5
5
|
|
6
6
|
|
7
|
-
def
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
)
|
12
|
-
for manifold_market in manifold_markets:
|
13
|
-
if manifold_market.question == question:
|
14
|
-
return manifold_market.resolution
|
15
|
-
return None
|
7
|
+
def validate_resolution(v: t.Any) -> Resolution:
|
8
|
+
if isinstance(v, str):
|
9
|
+
return Resolution(outcome=OutcomeStr(v), invalid=False)
|
10
|
+
raise ValueError(f"Expected a string, got {v} {type(v)}")
|
@@ -69,6 +69,7 @@ MARKET_TYPE_TO_AGENT_MARKET: dict[MarketType, type[AgentMarket]] = {
|
|
69
69
|
MarketType.SEER: SeerAgentMarket,
|
70
70
|
}
|
71
71
|
|
72
|
+
|
72
73
|
JOB_MARKET_TYPE_TO_JOB_AGENT_MARKET: dict[MarketType, type[JobAgentMarket]] = {
|
73
74
|
MarketType.OMEN: OmenJobAgentMarket,
|
74
75
|
}
|
@@ -83,7 +84,7 @@ def get_binary_markets(
|
|
83
84
|
created_after: DatetimeUTC | None = None,
|
84
85
|
) -> t.Sequence[AgentMarket]:
|
85
86
|
agent_market_class = MARKET_TYPE_TO_AGENT_MARKET[market_type]
|
86
|
-
markets = agent_market_class.
|
87
|
+
markets = agent_market_class.get_markets(
|
87
88
|
limit=limit,
|
88
89
|
sort_by=sort_by,
|
89
90
|
filter_by=filter_by,
|
@@ -1,6 +1,11 @@
|
|
1
1
|
import typing as t
|
2
2
|
|
3
|
+
from pydantic import field_validator
|
4
|
+
from pydantic_core.core_schema import FieldValidationInfo
|
5
|
+
|
3
6
|
from prediction_market_agent_tooling.config import APIKeys
|
7
|
+
from prediction_market_agent_tooling.gtypes import OutcomeStr, Probability
|
8
|
+
from prediction_market_agent_tooling.loggers import logger
|
4
9
|
from prediction_market_agent_tooling.markets.agent_market import (
|
5
10
|
AgentMarket,
|
6
11
|
FilterBy,
|
@@ -32,14 +37,29 @@ class MetaculusAgentMarket(AgentMarket):
|
|
32
37
|
resolution_criteria: str
|
33
38
|
fees: MarketFees = MarketFees.get_zero_fees() # No fees on Metaculus.
|
34
39
|
|
40
|
+
@field_validator("probabilities")
|
41
|
+
def validate_probabilities(
|
42
|
+
cls,
|
43
|
+
probs: dict[OutcomeStr, Probability],
|
44
|
+
info: FieldValidationInfo,
|
45
|
+
) -> dict[OutcomeStr, Probability]:
|
46
|
+
outcomes: t.Sequence[OutcomeStr] = check_not_none(info.data.get("outcomes"))
|
47
|
+
# We don't check for outcomes match because Metaculus has no filled outcomes.
|
48
|
+
total = float(sum(probs.values()))
|
49
|
+
if not 0.999 <= total <= 1.001:
|
50
|
+
# We simply log a warning because for some use-cases (e.g. existing positions), the
|
51
|
+
# markets might be already closed hence no reliable outcome token prices exist anymore.
|
52
|
+
logger.warning(f"Probabilities for market {info.data=} do not sum to 1.")
|
53
|
+
return probs
|
54
|
+
|
35
55
|
@staticmethod
|
36
56
|
def from_data_model(model: MetaculusQuestion) -> "MetaculusAgentMarket":
|
57
|
+
probabilities = AgentMarket.build_probability_map_from_p_yes(p_yes=model.p_yes)
|
37
58
|
return MetaculusAgentMarket(
|
38
59
|
id=str(model.id),
|
39
60
|
question=model.title,
|
40
61
|
outcomes=[],
|
41
62
|
resolution=None,
|
42
|
-
current_p_yes=model.p_yes,
|
43
63
|
created_time=model.created_at,
|
44
64
|
close_time=model.scheduled_close_time,
|
45
65
|
url=model.page_url,
|
@@ -49,10 +69,11 @@ class MetaculusAgentMarket(AgentMarket):
|
|
49
69
|
description=model.question.description,
|
50
70
|
fine_print=model.question.fine_print,
|
51
71
|
resolution_criteria=model.question.resolution_criteria,
|
72
|
+
probabilities=probabilities,
|
52
73
|
)
|
53
74
|
|
54
75
|
@staticmethod
|
55
|
-
def
|
76
|
+
def get_markets(
|
56
77
|
limit: int,
|
57
78
|
sort_by: SortBy = SortBy.NONE,
|
58
79
|
filter_by: FilterBy = FilterBy.OPEN,
|
@@ -109,8 +130,12 @@ class MetaculusAgentMarket(AgentMarket):
|
|
109
130
|
def store_prediction(
|
110
131
|
self, processed_market: ProcessedMarket | None, keys: APIKeys, agent_name: str
|
111
132
|
) -> None:
|
112
|
-
if
|
113
|
-
|
133
|
+
if (
|
134
|
+
processed_market is not None
|
135
|
+
and processed_market.answer.get_yes_probability() is not None
|
136
|
+
):
|
137
|
+
yes_prob = check_not_none(processed_market.answer.get_yes_probability())
|
138
|
+
make_prediction(self.id, yes_prob)
|
114
139
|
post_question_comment(
|
115
140
|
self.id,
|
116
141
|
check_not_none(
|
@@ -63,7 +63,7 @@ def get_boolean_outcome(outcome_str: str) -> bool:
|
|
63
63
|
raise ValueError(f"Outcome `{outcome_str}` is not a valid boolean outcome.")
|
64
64
|
|
65
65
|
|
66
|
-
def get_bet_outcome(binary_outcome: bool) ->
|
66
|
+
def get_bet_outcome(binary_outcome: bool) -> OutcomeStr:
|
67
67
|
return OMEN_TRUE_OUTCOME if binary_outcome else OMEN_FALSE_OUTCOME
|
68
68
|
|
69
69
|
|
@@ -399,31 +399,16 @@ class OmenMarket(BaseModel):
|
|
399
399
|
def is_binary(self) -> bool:
|
400
400
|
return len(self.outcomes) == 2
|
401
401
|
|
402
|
-
def
|
403
|
-
if
|
404
|
-
|
405
|
-
|
406
|
-
)
|
407
|
-
outcome: str = self.outcomes[answer.as_int()]
|
408
|
-
return get_boolean_outcome(outcome)
|
409
|
-
|
410
|
-
@property
|
411
|
-
def boolean_outcome(self) -> bool:
|
412
|
-
if not self.is_resolved_with_valid_answer:
|
413
|
-
raise ValueError(f"Bet with title {self.title} is not resolved.")
|
414
|
-
return self.boolean_outcome_from_answer(
|
415
|
-
check_not_none(
|
416
|
-
self.currentAnswer, "Can not be None if `is_resolved_with_valid_answer`"
|
417
|
-
)
|
418
|
-
)
|
402
|
+
def outcome_from_answer(self, answer: HexBytes) -> OutcomeStr | None:
|
403
|
+
if answer == INVALID_ANSWER_HEX_BYTES:
|
404
|
+
return None
|
405
|
+
return self.outcomes[answer.as_int()]
|
419
406
|
|
420
407
|
def get_resolution_enum_from_answer(self, answer: HexBytes) -> Resolution:
|
421
|
-
if
|
422
|
-
return Resolution.
|
423
|
-
|
424
|
-
|
425
|
-
else:
|
426
|
-
return Resolution.NO
|
408
|
+
if outcome := self.outcome_from_answer(answer):
|
409
|
+
return Resolution.from_answer(outcome)
|
410
|
+
|
411
|
+
return Resolution(outcome=None, invalid=True)
|
427
412
|
|
428
413
|
def get_resolution_enum(self) -> t.Optional[Resolution]:
|
429
414
|
if not self.is_resolved_with_valid_answer:
|
@@ -561,10 +546,6 @@ class OmenBet(BaseModel):
|
|
561
546
|
def creation_datetime(self) -> DatetimeUTC:
|
562
547
|
return DatetimeUTC.to_datetime_utc(self.creationTimestamp)
|
563
548
|
|
564
|
-
@property
|
565
|
-
def boolean_outcome(self) -> bool:
|
566
|
-
return get_boolean_outcome(self.fpmm.outcomes[self.outcomeIndex])
|
567
|
-
|
568
549
|
@property
|
569
550
|
def old_probability(self) -> Probability:
|
570
551
|
# Old marginal price is the probability of the outcome before placing this bet.
|
@@ -582,9 +563,13 @@ class OmenBet(BaseModel):
|
|
582
563
|
|
583
564
|
def get_profit(self) -> CollateralToken:
|
584
565
|
bet_amount = self.collateral_amount_token
|
566
|
+
|
567
|
+
if not self.fpmm.has_valid_answer:
|
568
|
+
return CollateralToken(0)
|
569
|
+
|
585
570
|
profit = (
|
586
571
|
self.outcomeTokensTraded.as_outcome_token.as_token - bet_amount
|
587
|
-
if self.
|
572
|
+
if self.outcomeIndex == self.fpmm.answer_index
|
588
573
|
else -bet_amount
|
589
574
|
)
|
590
575
|
return profit
|
@@ -594,7 +579,7 @@ class OmenBet(BaseModel):
|
|
594
579
|
id=str(self.transactionHash),
|
595
580
|
# Use the transaction hash instead of the bet id - both are valid, but we return the transaction hash from the trade functions, so be consistent here.
|
596
581
|
amount=self.collateral_amount_token,
|
597
|
-
outcome=self.
|
582
|
+
outcome=self.fpmm.outcomes[self.outcomeIndex],
|
598
583
|
created_time=self.creation_datetime,
|
599
584
|
market_question=self.title,
|
600
585
|
market_id=self.fpmm.id,
|
@@ -610,11 +595,11 @@ class OmenBet(BaseModel):
|
|
610
595
|
id=self.transactionHash.hex(),
|
611
596
|
# Use the transaction hash instead of the bet id - both are valid, but we return the transaction hash from the trade functions, so be consistent here.
|
612
597
|
amount=self.collateral_amount_token,
|
613
|
-
outcome=self.
|
598
|
+
outcome=self.fpmm.outcomes[self.outcomeIndex],
|
614
599
|
created_time=self.creation_datetime,
|
615
600
|
market_question=self.title,
|
616
601
|
market_id=self.fpmm.id,
|
617
|
-
market_outcome=self.fpmm.
|
602
|
+
market_outcome=self.fpmm.outcomes[self.outcomeIndex],
|
618
603
|
resolved_time=check_not_none(self.fpmm.finalized_datetime),
|
619
604
|
profit=self.get_profit(),
|
620
605
|
)
|