prediction-market-agent-tooling 0.56.0.dev1863__py3-none-any.whl → 0.56.2.dev129__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/config.py +1 -1
- prediction_market_agent_tooling/deploy/agent.py +23 -12
- prediction_market_agent_tooling/jobs/jobs_models.py +27 -2
- prediction_market_agent_tooling/jobs/omen/omen_jobs.py +67 -41
- prediction_market_agent_tooling/markets/agent_market.py +8 -2
- prediction_market_agent_tooling/markets/base_subgraph_handler.py +51 -0
- prediction_market_agent_tooling/markets/markets.py +12 -0
- prediction_market_agent_tooling/markets/metaculus/metaculus.py +1 -1
- prediction_market_agent_tooling/markets/omen/data_models.py +11 -2
- prediction_market_agent_tooling/markets/omen/omen.py +16 -9
- prediction_market_agent_tooling/markets/omen/omen_subgraph_handler.py +29 -51
- prediction_market_agent_tooling/markets/seer/data_models.py +27 -0
- prediction_market_agent_tooling/markets/seer/seer_subgraph_handler.py +142 -0
- prediction_market_agent_tooling/tools/caches/db_cache.py +39 -67
- prediction_market_agent_tooling/tools/is_invalid.py +1 -1
- prediction_market_agent_tooling/tools/utils.py +2 -0
- {prediction_market_agent_tooling-0.56.0.dev1863.dist-info → prediction_market_agent_tooling-0.56.2.dev129.dist-info}/METADATA +1 -1
- {prediction_market_agent_tooling-0.56.0.dev1863.dist-info → prediction_market_agent_tooling-0.56.2.dev129.dist-info}/RECORD +21 -19
- prediction_market_agent_tooling/jobs/jobs.py +0 -45
- {prediction_market_agent_tooling-0.56.0.dev1863.dist-info → prediction_market_agent_tooling-0.56.2.dev129.dist-info}/LICENSE +0 -0
- {prediction_market_agent_tooling-0.56.0.dev1863.dist-info → prediction_market_agent_tooling-0.56.2.dev129.dist-info}/WHEEL +0 -0
- {prediction_market_agent_tooling-0.56.0.dev1863.dist-info → prediction_market_agent_tooling-0.56.2.dev129.dist-info}/entry_points.txt +0 -0
@@ -153,10 +153,10 @@ class DeployableAgent:
|
|
153
153
|
input=input,
|
154
154
|
output=output,
|
155
155
|
user_id=user_id or getpass.getuser(),
|
156
|
-
session_id=session_id
|
157
|
-
|
158
|
-
version=version
|
159
|
-
|
156
|
+
session_id=session_id or self.session_id,
|
157
|
+
# All traces within a single run execution will be grouped under a single session.
|
158
|
+
version=version or APIKeys().LANGFUSE_DEPLOYMENT_VERSION,
|
159
|
+
# Optionally, mark the current deployment with version (e.g. add git commit hash during docker building).
|
160
160
|
release=release,
|
161
161
|
metadata=metadata,
|
162
162
|
tags=tags,
|
@@ -342,6 +342,10 @@ class DeployablePredictionAgent(DeployableAgent):
|
|
342
342
|
]
|
343
343
|
)
|
344
344
|
|
345
|
+
@property
|
346
|
+
def agent_name(self) -> str:
|
347
|
+
return self.__class__.__name__
|
348
|
+
|
345
349
|
def check_min_required_balance_to_operate(self, market_type: MarketType) -> None:
|
346
350
|
api_keys = APIKeys()
|
347
351
|
|
@@ -444,7 +448,9 @@ class DeployablePredictionAgent(DeployableAgent):
|
|
444
448
|
) -> None:
|
445
449
|
keys = APIKeys()
|
446
450
|
if self.store_prediction:
|
447
|
-
market.store_prediction(
|
451
|
+
market.store_prediction(
|
452
|
+
processed_market=processed_market, keys=keys, agent_name=self.agent_name
|
453
|
+
)
|
448
454
|
else:
|
449
455
|
logger.info(
|
450
456
|
f"Prediction {processed_market} not stored because {self.store_prediction=}."
|
@@ -519,7 +525,6 @@ class DeployableTraderAgent(DeployablePredictionAgent):
|
|
519
525
|
def initialize_langfuse(self) -> None:
|
520
526
|
super().initialize_langfuse()
|
521
527
|
# Auto-observe all the methods where it makes sense, so that subclassses don't need to do it manually.
|
522
|
-
self.get_betting_strategy = observe()(self.get_betting_strategy) # type: ignore[method-assign]
|
523
528
|
self.build_trades = observe()(self.build_trades) # type: ignore[method-assign]
|
524
529
|
|
525
530
|
def check_min_required_balance_to_trade(self, market: AgentMarket) -> None:
|
@@ -555,16 +560,18 @@ class DeployableTraderAgent(DeployablePredictionAgent):
|
|
555
560
|
BettingStrategy.assert_trades_currency_match_markets(market, trades)
|
556
561
|
return trades
|
557
562
|
|
563
|
+
def before_process_market(
|
564
|
+
self, market_type: MarketType, market: AgentMarket
|
565
|
+
) -> None:
|
566
|
+
super().before_process_market(market_type, market)
|
567
|
+
self.check_min_required_balance_to_trade(market)
|
568
|
+
|
558
569
|
def process_market(
|
559
570
|
self,
|
560
571
|
market_type: MarketType,
|
561
572
|
market: AgentMarket,
|
562
573
|
verify_market: bool = True,
|
563
574
|
) -> ProcessedTradedMarket | None:
|
564
|
-
self.check_min_required_balance_to_trade(
|
565
|
-
market
|
566
|
-
) # Do this as part of `process_market` because it observes some methods --> To keep everything traced under the `process_market`.
|
567
|
-
|
568
575
|
processed_market = super().process_market(market_type, market, verify_market)
|
569
576
|
if processed_market is None:
|
570
577
|
return None
|
@@ -612,10 +619,14 @@ class DeployableTraderAgent(DeployablePredictionAgent):
|
|
612
619
|
processed_market: ProcessedMarket | None,
|
613
620
|
) -> None:
|
614
621
|
api_keys = APIKeys()
|
615
|
-
super().after_process_market(
|
622
|
+
super().after_process_market(
|
623
|
+
market_type,
|
624
|
+
market,
|
625
|
+
processed_market,
|
626
|
+
)
|
616
627
|
if isinstance(processed_market, ProcessedTradedMarket):
|
617
628
|
if self.store_trades:
|
618
|
-
market.store_trades(processed_market, api_keys)
|
629
|
+
market.store_trades(processed_market, api_keys, self.agent_name)
|
619
630
|
else:
|
620
631
|
logger.info(
|
621
632
|
f"Trades {processed_market.trades} not stored because {self.store_trades=}."
|
@@ -3,7 +3,12 @@ from abc import ABC, abstractmethod
|
|
3
3
|
|
4
4
|
from pydantic import BaseModel
|
5
5
|
|
6
|
-
from prediction_market_agent_tooling.
|
6
|
+
from prediction_market_agent_tooling.deploy.betting_strategy import ProbabilisticAnswer
|
7
|
+
from prediction_market_agent_tooling.gtypes import Probability
|
8
|
+
from prediction_market_agent_tooling.markets.agent_market import (
|
9
|
+
AgentMarket,
|
10
|
+
ProcessedTradedMarket,
|
11
|
+
)
|
7
12
|
from prediction_market_agent_tooling.markets.omen.omen_subgraph_handler import (
|
8
13
|
FilterBy,
|
9
14
|
SortBy,
|
@@ -39,10 +44,24 @@ class JobAgentMarket(AgentMarket, ABC):
|
|
39
44
|
@classmethod
|
40
45
|
@abstractmethod
|
41
46
|
def get_jobs(
|
42
|
-
cls,
|
47
|
+
cls,
|
48
|
+
limit: int | None,
|
49
|
+
filter_by: FilterBy = FilterBy.OPEN,
|
50
|
+
sort_by: SortBy = SortBy.CLOSING_SOONEST,
|
43
51
|
) -> t.Sequence["JobAgentMarket"]:
|
44
52
|
"""Get all available jobs."""
|
45
53
|
|
54
|
+
@staticmethod
|
55
|
+
@abstractmethod
|
56
|
+
def get_job(id: str) -> "JobAgentMarket":
|
57
|
+
"""Get a single job by its id."""
|
58
|
+
|
59
|
+
@abstractmethod
|
60
|
+
def submit_job_result(
|
61
|
+
self, agent_name: str, max_bond: float, result: str
|
62
|
+
) -> ProcessedTradedMarket:
|
63
|
+
"""Submit the completed result for this job."""
|
64
|
+
|
46
65
|
def to_simple_job(self, max_bond: float) -> SimpleJob:
|
47
66
|
return SimpleJob(
|
48
67
|
id=self.id,
|
@@ -51,3 +70,9 @@ class JobAgentMarket(AgentMarket, ABC):
|
|
51
70
|
currency=self.currency.value,
|
52
71
|
deadline=self.deadline,
|
53
72
|
)
|
73
|
+
|
74
|
+
def get_job_answer(self, result: str) -> ProbabilisticAnswer:
|
75
|
+
# Just return 100% yes with 100% confidence, because we assume the job is completed correctly.
|
76
|
+
return ProbabilisticAnswer(
|
77
|
+
p_yes=Probability(1.0), confidence=1.0, reasoning=result
|
78
|
+
)
|
@@ -1,15 +1,14 @@
|
|
1
1
|
import typing as t
|
2
2
|
|
3
|
-
from
|
4
|
-
|
3
|
+
from prediction_market_agent_tooling.config import APIKeys
|
5
4
|
from prediction_market_agent_tooling.deploy.betting_strategy import (
|
6
5
|
Currency,
|
7
6
|
KellyBettingStrategy,
|
8
|
-
ProbabilisticAnswer,
|
9
7
|
TradeType,
|
10
8
|
)
|
11
|
-
from prediction_market_agent_tooling.gtypes import Probability
|
12
9
|
from prediction_market_agent_tooling.jobs.jobs_models import JobAgentMarket
|
10
|
+
from prediction_market_agent_tooling.markets.agent_market import ProcessedTradedMarket
|
11
|
+
from prediction_market_agent_tooling.markets.data_models import PlacedTrade, Trade
|
13
12
|
from prediction_market_agent_tooling.markets.omen.omen import (
|
14
13
|
BetAmount,
|
15
14
|
OmenAgentMarket,
|
@@ -36,11 +35,27 @@ class OmenJobAgentMarket(OmenAgentMarket, JobAgentMarket):
|
|
36
35
|
return self.close_time
|
37
36
|
|
38
37
|
def get_reward(self, max_bond: float) -> float:
|
39
|
-
|
38
|
+
trade = self.get_job_trade(
|
39
|
+
max_bond,
|
40
|
+
result="", # Pass empty result, as we are computing only potential reward at this point.
|
41
|
+
)
|
42
|
+
reward = (
|
43
|
+
self.get_buy_token_amount(
|
44
|
+
bet_amount=BetAmount(
|
45
|
+
amount=trade.amount.amount, currency=trade.amount.currency
|
46
|
+
),
|
47
|
+
direction=trade.outcome,
|
48
|
+
).amount
|
49
|
+
- trade.amount.amount
|
50
|
+
)
|
51
|
+
return reward
|
40
52
|
|
41
53
|
@classmethod
|
42
54
|
def get_jobs(
|
43
|
-
cls,
|
55
|
+
cls,
|
56
|
+
limit: int | None,
|
57
|
+
filter_by: FilterBy = FilterBy.OPEN,
|
58
|
+
sort_by: SortBy = SortBy.CLOSING_SOONEST,
|
44
59
|
) -> t.Sequence["OmenJobAgentMarket"]:
|
45
60
|
markets = OmenSubgraphHandler().get_omen_binary_markets_simple(
|
46
61
|
limit=limit,
|
@@ -50,6 +65,52 @@ class OmenJobAgentMarket(OmenAgentMarket, JobAgentMarket):
|
|
50
65
|
)
|
51
66
|
return [OmenJobAgentMarket.from_omen_market(market) for market in markets]
|
52
67
|
|
68
|
+
@staticmethod
|
69
|
+
def get_job(id: str) -> "OmenJobAgentMarket":
|
70
|
+
return OmenJobAgentMarket.from_omen_agent_market(
|
71
|
+
OmenJobAgentMarket.get_binary_market(id=id)
|
72
|
+
)
|
73
|
+
|
74
|
+
def submit_job_result(
|
75
|
+
self, agent_name: str, max_bond: float, result: str
|
76
|
+
) -> ProcessedTradedMarket:
|
77
|
+
if not APIKeys().enable_ipfs_upload:
|
78
|
+
raise RuntimeError(
|
79
|
+
f"ENABLE_IPFS_UPLOAD must be set to True to upload job results."
|
80
|
+
)
|
81
|
+
|
82
|
+
trade = self.get_job_trade(max_bond, result)
|
83
|
+
buy_id = self.buy_tokens(outcome=trade.outcome, amount=trade.amount)
|
84
|
+
|
85
|
+
processed_traded_market = ProcessedTradedMarket(
|
86
|
+
answer=self.get_job_answer(result),
|
87
|
+
trades=[PlacedTrade.from_trade(trade, id=buy_id)],
|
88
|
+
)
|
89
|
+
|
90
|
+
keys = APIKeys()
|
91
|
+
self.store_trades(processed_traded_market, keys, agent_name)
|
92
|
+
|
93
|
+
return processed_traded_market
|
94
|
+
|
95
|
+
def get_job_trade(self, max_bond: float, result: str) -> Trade:
|
96
|
+
# Because jobs are powered by prediction markets, potentional reward depends on job's liquidity and our will to bond (bet) our xDai into our job completion.
|
97
|
+
strategy = KellyBettingStrategy(max_bet_amount=max_bond)
|
98
|
+
required_trades = strategy.calculate_trades(
|
99
|
+
existing_position=None,
|
100
|
+
answer=self.get_job_answer(result),
|
101
|
+
market=self,
|
102
|
+
)
|
103
|
+
assert (
|
104
|
+
len(required_trades) == 1
|
105
|
+
), f"Shouldn't process same job twice: {required_trades}"
|
106
|
+
trade = required_trades[0]
|
107
|
+
assert trade.trade_type == TradeType.BUY, "Should only buy on job markets."
|
108
|
+
assert trade.outcome, "Should buy only YES on job markets."
|
109
|
+
assert (
|
110
|
+
trade.amount.currency == Currency.xDai
|
111
|
+
), "Should work only on real-money markets."
|
112
|
+
return required_trades[0]
|
113
|
+
|
53
114
|
@staticmethod
|
54
115
|
def from_omen_market(market: OmenMarket) -> "OmenJobAgentMarket":
|
55
116
|
return OmenJobAgentMarket.from_omen_agent_market(
|
@@ -77,38 +138,3 @@ class OmenJobAgentMarket(OmenAgentMarket, JobAgentMarket):
|
|
77
138
|
finalized_time=market.finalized_time,
|
78
139
|
fees=market.fees,
|
79
140
|
)
|
80
|
-
|
81
|
-
|
82
|
-
def compute_job_reward(
|
83
|
-
market: OmenAgentMarket, max_bond: float, web3: Web3 | None = None
|
84
|
-
) -> float:
|
85
|
-
# Because jobs are powered by prediction markets, potentional reward depends on job's liquidity and our will to bond (bet) our xDai into our job completion.
|
86
|
-
strategy = KellyBettingStrategy(max_bet_amount=max_bond)
|
87
|
-
required_trades = strategy.calculate_trades(
|
88
|
-
existing_position=None,
|
89
|
-
# We assume that we finish the job and so the probability of the market happening will be 100%.
|
90
|
-
answer=ProbabilisticAnswer(p_yes=Probability(1.0), confidence=1.0),
|
91
|
-
market=market,
|
92
|
-
)
|
93
|
-
|
94
|
-
assert (
|
95
|
-
len(required_trades) == 1
|
96
|
-
), f"Shouldn't process same job twice: {required_trades}"
|
97
|
-
trade = required_trades[0]
|
98
|
-
assert trade.trade_type == TradeType.BUY, "Should only buy on job markets."
|
99
|
-
assert trade.outcome, "Should buy only YES on job markets."
|
100
|
-
assert (
|
101
|
-
trade.amount.currency == Currency.xDai
|
102
|
-
), "Should work only on real-money markets."
|
103
|
-
|
104
|
-
reward = (
|
105
|
-
market.get_buy_token_amount(
|
106
|
-
bet_amount=BetAmount(
|
107
|
-
amount=trade.amount.amount, currency=trade.amount.currency
|
108
|
-
),
|
109
|
-
direction=trade.outcome,
|
110
|
-
).amount
|
111
|
-
- trade.amount.amount
|
112
|
-
)
|
113
|
-
|
114
|
-
return reward
|
@@ -231,7 +231,10 @@ class AgentMarket(BaseModel):
|
|
231
231
|
raise NotImplementedError("Subclasses must implement this method")
|
232
232
|
|
233
233
|
def store_prediction(
|
234
|
-
self,
|
234
|
+
self,
|
235
|
+
processed_market: ProcessedMarket | None,
|
236
|
+
keys: APIKeys,
|
237
|
+
agent_name: str,
|
235
238
|
) -> None:
|
236
239
|
"""
|
237
240
|
If market allows to upload predictions somewhere, implement it in this method.
|
@@ -239,7 +242,10 @@ class AgentMarket(BaseModel):
|
|
239
242
|
raise NotImplementedError("Subclasses must implement this method")
|
240
243
|
|
241
244
|
def store_trades(
|
242
|
-
self,
|
245
|
+
self,
|
246
|
+
traded_market: ProcessedTradedMarket | None,
|
247
|
+
keys: APIKeys,
|
248
|
+
agent_name: str,
|
243
249
|
) -> None:
|
244
250
|
"""
|
245
251
|
If market allows to upload trades somewhere, implement it in this method.
|
@@ -0,0 +1,51 @@
|
|
1
|
+
import typing as t
|
2
|
+
|
3
|
+
import tenacity
|
4
|
+
from pydantic import BaseModel
|
5
|
+
from subgrounds import FieldPath, Subgrounds
|
6
|
+
|
7
|
+
from prediction_market_agent_tooling.config import APIKeys
|
8
|
+
from prediction_market_agent_tooling.loggers import logger
|
9
|
+
from prediction_market_agent_tooling.tools.singleton import SingletonMeta
|
10
|
+
|
11
|
+
T = t.TypeVar("T", bound=BaseModel)
|
12
|
+
|
13
|
+
|
14
|
+
class BaseSubgraphHandler(metaclass=SingletonMeta):
|
15
|
+
def __init__(self) -> None:
|
16
|
+
self.sg = Subgrounds()
|
17
|
+
# Patch methods to retry on failure.
|
18
|
+
self.sg.query_json = tenacity.retry(
|
19
|
+
stop=tenacity.stop_after_attempt(3),
|
20
|
+
wait=tenacity.wait_fixed(1),
|
21
|
+
after=lambda x: logger.debug(f"query_json failed, {x.attempt_number=}."),
|
22
|
+
)(self.sg.query_json)
|
23
|
+
self.sg.load_subgraph = tenacity.retry(
|
24
|
+
stop=tenacity.stop_after_attempt(3),
|
25
|
+
wait=tenacity.wait_fixed(1),
|
26
|
+
after=lambda x: logger.debug(f"load_subgraph failed, {x.attempt_number=}."),
|
27
|
+
)(self.sg.load_subgraph)
|
28
|
+
|
29
|
+
self.keys = APIKeys()
|
30
|
+
|
31
|
+
def _parse_items_from_json(
|
32
|
+
self, result: list[dict[str, t.Any]]
|
33
|
+
) -> list[dict[str, t.Any]]:
|
34
|
+
"""subgrounds return a weird key as a dict key"""
|
35
|
+
items = []
|
36
|
+
for result_chunk in result:
|
37
|
+
for k, v in result_chunk.items():
|
38
|
+
# subgrounds might pack all items as a list, indexed by a key, or pack it as a dictionary (if one single element)
|
39
|
+
if v is None:
|
40
|
+
continue
|
41
|
+
elif isinstance(v, dict):
|
42
|
+
items.extend([v])
|
43
|
+
else:
|
44
|
+
items.extend(v)
|
45
|
+
return items
|
46
|
+
|
47
|
+
def do_query(self, fields: list[FieldPath], pydantic_model: t.Type[T]) -> list[T]:
|
48
|
+
result = self.sg.query_json(fields)
|
49
|
+
items = self._parse_items_from_json(result)
|
50
|
+
models = [pydantic_model.model_validate(i) for i in items]
|
51
|
+
return models
|
@@ -3,6 +3,8 @@ from datetime import timedelta
|
|
3
3
|
from enum import Enum
|
4
4
|
|
5
5
|
from prediction_market_agent_tooling.config import APIKeys
|
6
|
+
from prediction_market_agent_tooling.jobs.jobs_models import JobAgentMarket
|
7
|
+
from prediction_market_agent_tooling.jobs.omen.omen_jobs import OmenJobAgentMarket
|
6
8
|
from prediction_market_agent_tooling.markets.agent_market import (
|
7
9
|
AgentMarket,
|
8
10
|
FilterBy,
|
@@ -46,6 +48,12 @@ class MarketType(str, Enum):
|
|
46
48
|
raise ValueError(f"Unknown market type: {self}")
|
47
49
|
return MARKET_TYPE_TO_AGENT_MARKET[self]
|
48
50
|
|
51
|
+
@property
|
52
|
+
def job_class(self) -> type[JobAgentMarket]:
|
53
|
+
if self not in JOB_MARKET_TYPE_TO_JOB_AGENT_MARKET:
|
54
|
+
raise ValueError(f"Unknown market type: {self}")
|
55
|
+
return JOB_MARKET_TYPE_TO_JOB_AGENT_MARKET[self]
|
56
|
+
|
49
57
|
@property
|
50
58
|
def is_blockchain_market(self) -> bool:
|
51
59
|
return self in [MarketType.OMEN, MarketType.POLYMARKET]
|
@@ -58,6 +66,10 @@ MARKET_TYPE_TO_AGENT_MARKET: dict[MarketType, type[AgentMarket]] = {
|
|
58
66
|
MarketType.METACULUS: MetaculusAgentMarket,
|
59
67
|
}
|
60
68
|
|
69
|
+
JOB_MARKET_TYPE_TO_JOB_AGENT_MARKET: dict[MarketType, type[JobAgentMarket]] = {
|
70
|
+
MarketType.OMEN: OmenJobAgentMarket,
|
71
|
+
}
|
72
|
+
|
61
73
|
|
62
74
|
def get_binary_markets(
|
63
75
|
limit: int,
|
@@ -107,7 +107,7 @@ class MetaculusAgentMarket(AgentMarket):
|
|
107
107
|
return [MetaculusAgentMarket.from_data_model(q) for q in all_questions[:limit]]
|
108
108
|
|
109
109
|
def store_prediction(
|
110
|
-
self, processed_market: ProcessedMarket | None, keys: APIKeys
|
110
|
+
self, processed_market: ProcessedMarket | None, keys: APIKeys, agent_name: str
|
111
111
|
) -> None:
|
112
112
|
if processed_market is not None:
|
113
113
|
make_prediction(self.id, processed_market.answer.p_yes)
|
@@ -24,6 +24,7 @@ from prediction_market_agent_tooling.markets.data_models import (
|
|
24
24
|
ResolvedBet,
|
25
25
|
)
|
26
26
|
from prediction_market_agent_tooling.tools.utils import (
|
27
|
+
BPS_CONSTANT,
|
27
28
|
DatetimeUTC,
|
28
29
|
check_not_none,
|
29
30
|
should_not_happen,
|
@@ -502,7 +503,7 @@ class OmenBet(BaseModel):
|
|
502
503
|
feeAmount: Wei
|
503
504
|
outcomeIndex: int
|
504
505
|
outcomeTokensTraded: Wei
|
505
|
-
transactionHash:
|
506
|
+
transactionHash: HexBytes
|
506
507
|
fpmm: OmenMarket
|
507
508
|
|
508
509
|
@property
|
@@ -785,6 +786,14 @@ class ContractPrediction(BaseModel):
|
|
785
786
|
tx_hashes: list[HexBytes] = Field(..., alias="txHashes")
|
786
787
|
estimated_probability_bps: int = Field(..., alias="estimatedProbabilityBps")
|
787
788
|
|
789
|
+
@property
|
790
|
+
def estimated_probability(self) -> Probability:
|
791
|
+
return Probability(self.estimated_probability_bps / BPS_CONSTANT)
|
792
|
+
|
793
|
+
@property
|
794
|
+
def boolean_outcome(self) -> bool:
|
795
|
+
return self.estimated_probability > 0.5
|
796
|
+
|
788
797
|
@computed_field # type: ignore[prop-decorator] # Mypy issue: https://github.com/python/mypy/issues/14461
|
789
798
|
@property
|
790
799
|
def publisher_checksummed(self) -> ChecksumAddress:
|
@@ -798,7 +807,7 @@ class ContractPrediction(BaseModel):
|
|
798
807
|
|
799
808
|
class IPFSAgentResult(BaseModel):
|
800
809
|
reasoning: str
|
801
|
-
|
810
|
+
agent_name: str
|
802
811
|
model_config = ConfigDict(
|
803
812
|
extra="forbid",
|
804
813
|
)
|
@@ -78,6 +78,7 @@ from prediction_market_agent_tooling.tools.contract import (
|
|
78
78
|
from prediction_market_agent_tooling.tools.hexbytes_custom import HexBytes
|
79
79
|
from prediction_market_agent_tooling.tools.ipfs.ipfs_handler import IPFSHandler
|
80
80
|
from prediction_market_agent_tooling.tools.utils import (
|
81
|
+
BPS_CONSTANT,
|
81
82
|
DatetimeUTC,
|
82
83
|
calculate_sell_amount_in_collateral,
|
83
84
|
check_not_none,
|
@@ -413,17 +414,21 @@ class OmenAgentMarket(AgentMarket):
|
|
413
414
|
@staticmethod
|
414
415
|
def verify_operational_balance(api_keys: APIKeys) -> bool:
|
415
416
|
return get_total_balance(
|
416
|
-
api_keys.public_key,
|
417
|
+
api_keys.public_key,
|
418
|
+
# Use `public_key`, not `bet_from_address` because transaction costs are paid from the EOA wallet.
|
417
419
|
sum_wxdai=False,
|
418
420
|
) > xdai_type(0.001)
|
419
421
|
|
420
422
|
def store_prediction(
|
421
|
-
self, processed_market: ProcessedMarket | None, keys: APIKeys
|
423
|
+
self, processed_market: ProcessedMarket | None, keys: APIKeys, agent_name: str
|
422
424
|
) -> None:
|
423
425
|
"""On Omen, we have to store predictions along with trades, see `store_trades`."""
|
424
426
|
|
425
427
|
def store_trades(
|
426
|
-
self,
|
428
|
+
self,
|
429
|
+
traded_market: ProcessedTradedMarket | None,
|
430
|
+
keys: APIKeys,
|
431
|
+
agent_name: str,
|
427
432
|
) -> None:
|
428
433
|
if traded_market is None:
|
429
434
|
logger.warning(f"No prediction for market {self.id}, not storing anything.")
|
@@ -437,7 +442,7 @@ class OmenAgentMarket(AgentMarket):
|
|
437
442
|
if keys.enable_ipfs_upload:
|
438
443
|
logger.info("Storing prediction on IPFS.")
|
439
444
|
ipfs_hash = IPFSHandler(keys).store_agent_result(
|
440
|
-
IPFSAgentResult(reasoning=reasoning)
|
445
|
+
IPFSAgentResult(reasoning=reasoning, agent_name=agent_name)
|
441
446
|
)
|
442
447
|
ipfs_hash_decoded = ipfscidv0_to_byte32(ipfs_hash)
|
443
448
|
|
@@ -448,7 +453,7 @@ class OmenAgentMarket(AgentMarket):
|
|
448
453
|
publisher=keys.public_key,
|
449
454
|
ipfs_hash=ipfs_hash_decoded,
|
450
455
|
tx_hashes=tx_hashes,
|
451
|
-
estimated_probability_bps=int(traded_market.answer.p_yes *
|
456
|
+
estimated_probability_bps=int(traded_market.answer.p_yes * BPS_CONSTANT),
|
452
457
|
)
|
453
458
|
tx_receipt = OmenAgentResultMappingContract().add_prediction(
|
454
459
|
api_keys=keys,
|
@@ -1237,12 +1242,12 @@ def redeem_from_all_user_positions(
|
|
1237
1242
|
|
1238
1243
|
if not conditional_token_contract.is_condition_resolved(condition_id):
|
1239
1244
|
logger.info(
|
1240
|
-
f"[{index+1} / {len(user_positions)}] Skipping redeem, {user_position.id=} isn't resolved yet."
|
1245
|
+
f"[{index + 1} / {len(user_positions)}] Skipping redeem, {user_position.id=} isn't resolved yet."
|
1241
1246
|
)
|
1242
1247
|
continue
|
1243
1248
|
|
1244
1249
|
logger.info(
|
1245
|
-
f"[{index+1} / {len(user_positions)}] Processing redeem from {user_position.id=}."
|
1250
|
+
f"[{index + 1} / {len(user_positions)}] Processing redeem from {user_position.id=}."
|
1246
1251
|
)
|
1247
1252
|
|
1248
1253
|
original_balances = get_balances(public_key, web3)
|
@@ -1263,9 +1268,11 @@ def redeem_from_all_user_positions(
|
|
1263
1268
|
def get_binary_market_p_yes_history(market: OmenAgentMarket) -> list[Probability]:
|
1264
1269
|
history: list[Probability] = []
|
1265
1270
|
trades = sorted(
|
1266
|
-
OmenSubgraphHandler().get_trades(
|
1271
|
+
OmenSubgraphHandler().get_trades(
|
1272
|
+
# We need to look at price both after buying or selling, so get trades, not bets.
|
1267
1273
|
market_id=market.market_maker_contract_address_checksummed,
|
1268
|
-
end_time=market.close_time,
|
1274
|
+
end_time=market.close_time,
|
1275
|
+
# Even after market is closed, there can be many `Sell` trades which will converge the probability to the true one.
|
1269
1276
|
),
|
1270
1277
|
key=lambda x: x.creation_datetime,
|
1271
1278
|
)
|
@@ -2,12 +2,10 @@ import sys
|
|
2
2
|
import typing as t
|
3
3
|
|
4
4
|
import requests
|
5
|
-
import tenacity
|
6
5
|
from PIL import Image
|
7
6
|
from PIL.Image import Image as ImageType
|
8
|
-
from subgrounds import FieldPath
|
7
|
+
from subgrounds import FieldPath
|
9
8
|
|
10
|
-
from prediction_market_agent_tooling.config import APIKeys
|
11
9
|
from prediction_market_agent_tooling.gtypes import (
|
12
10
|
ChecksumAddress,
|
13
11
|
HexAddress,
|
@@ -15,8 +13,10 @@ from prediction_market_agent_tooling.gtypes import (
|
|
15
13
|
Wei,
|
16
14
|
wei_type,
|
17
15
|
)
|
18
|
-
from prediction_market_agent_tooling.loggers import logger
|
19
16
|
from prediction_market_agent_tooling.markets.agent_market import FilterBy, SortBy
|
17
|
+
from prediction_market_agent_tooling.markets.base_subgraph_handler import (
|
18
|
+
BaseSubgraphHandler,
|
19
|
+
)
|
20
20
|
from prediction_market_agent_tooling.markets.omen.data_models import (
|
21
21
|
OMEN_BINARY_MARKET_OUTCOMES,
|
22
22
|
ContractPrediction,
|
@@ -33,7 +33,6 @@ from prediction_market_agent_tooling.markets.omen.omen_contracts import (
|
|
33
33
|
WrappedxDaiContract,
|
34
34
|
sDaiContract,
|
35
35
|
)
|
36
|
-
from prediction_market_agent_tooling.tools.singleton import SingletonMeta
|
37
36
|
from prediction_market_agent_tooling.tools.utils import (
|
38
37
|
DatetimeUTC,
|
39
38
|
to_int_timestamp,
|
@@ -51,7 +50,7 @@ SAFE_COLLATERAL_TOKEN_MARKETS = (
|
|
51
50
|
)
|
52
51
|
|
53
52
|
|
54
|
-
class OmenSubgraphHandler(
|
53
|
+
class OmenSubgraphHandler(BaseSubgraphHandler):
|
55
54
|
"""
|
56
55
|
Class responsible for handling interactions with Omen subgraphs (trades, conditionalTokens).
|
57
56
|
"""
|
@@ -64,52 +63,38 @@ class OmenSubgraphHandler(metaclass=SingletonMeta):
|
|
64
63
|
|
65
64
|
OMEN_IMAGE_MAPPING_GRAPH_URL = "https://gateway-arbitrum.network.thegraph.com/api/{graph_api_key}/subgraphs/id/EWN14ciGK53PpUiKSm7kMWQ6G4iz3tDrRLyZ1iXMQEdu"
|
66
65
|
|
67
|
-
OMEN_AGENT_RESULT_MAPPING_GRAPH_URL = "https://gateway-arbitrum.network.thegraph.com/api/{graph_api_key}/subgraphs/id/
|
66
|
+
OMEN_AGENT_RESULT_MAPPING_GRAPH_URL = "https://gateway-arbitrum.network.thegraph.com/api/{graph_api_key}/subgraphs/id/J6bJEnbqJpAvNyQE8i58M9mKF4zqo33BEJRdnXmqa6Kn"
|
68
67
|
|
69
68
|
INVALID_ANSWER = "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"
|
70
69
|
|
71
70
|
def __init__(self) -> None:
|
72
|
-
|
73
|
-
|
74
|
-
# Patch methods to retry on failure.
|
75
|
-
self.sg.query_json = tenacity.retry(
|
76
|
-
stop=tenacity.stop_after_attempt(3),
|
77
|
-
wait=tenacity.wait_fixed(1),
|
78
|
-
after=lambda x: logger.debug(f"query_json failed, {x.attempt_number=}."),
|
79
|
-
)(self.sg.query_json)
|
80
|
-
self.sg.load_subgraph = tenacity.retry(
|
81
|
-
stop=tenacity.stop_after_attempt(3),
|
82
|
-
wait=tenacity.wait_fixed(1),
|
83
|
-
after=lambda x: logger.debug(f"load_subgraph failed, {x.attempt_number=}."),
|
84
|
-
)(self.sg.load_subgraph)
|
85
|
-
|
86
|
-
keys = APIKeys()
|
71
|
+
super().__init__()
|
87
72
|
|
88
73
|
# Load the subgraph
|
89
74
|
self.trades_subgraph = self.sg.load_subgraph(
|
90
75
|
self.OMEN_TRADES_SUBGRAPH.format(
|
91
|
-
graph_api_key=keys.graph_api_key.get_secret_value()
|
76
|
+
graph_api_key=self.keys.graph_api_key.get_secret_value()
|
92
77
|
)
|
93
78
|
)
|
94
79
|
self.conditional_tokens_subgraph = self.sg.load_subgraph(
|
95
80
|
self.CONDITIONAL_TOKENS_SUBGRAPH.format(
|
96
|
-
graph_api_key=keys.graph_api_key.get_secret_value()
|
81
|
+
graph_api_key=self.keys.graph_api_key.get_secret_value()
|
97
82
|
)
|
98
83
|
)
|
99
84
|
self.realityeth_subgraph = self.sg.load_subgraph(
|
100
85
|
self.REALITYETH_GRAPH_URL.format(
|
101
|
-
graph_api_key=keys.graph_api_key.get_secret_value()
|
86
|
+
graph_api_key=self.keys.graph_api_key.get_secret_value()
|
102
87
|
)
|
103
88
|
)
|
104
89
|
self.omen_image_mapping_subgraph = self.sg.load_subgraph(
|
105
90
|
self.OMEN_IMAGE_MAPPING_GRAPH_URL.format(
|
106
|
-
graph_api_key=keys.graph_api_key.get_secret_value()
|
91
|
+
graph_api_key=self.keys.graph_api_key.get_secret_value()
|
107
92
|
)
|
108
93
|
)
|
109
94
|
|
110
95
|
self.omen_agent_result_mapping_subgraph = self.sg.load_subgraph(
|
111
96
|
self.OMEN_AGENT_RESULT_MAPPING_GRAPH_URL.format(
|
112
|
-
graph_api_key=keys.graph_api_key.get_secret_value()
|
97
|
+
graph_api_key=self.keys.graph_api_key.get_secret_value()
|
113
98
|
)
|
114
99
|
)
|
115
100
|
|
@@ -446,14 +431,8 @@ class OmenSubgraphHandler(metaclass=SingletonMeta):
|
|
446
431
|
**optional_params,
|
447
432
|
)
|
448
433
|
|
449
|
-
omen_markets = self.do_markets_query(markets)
|
450
|
-
return omen_markets
|
451
|
-
|
452
|
-
def do_markets_query(self, markets: FieldPath) -> list[OmenMarket]:
|
453
434
|
fields = self._get_fields_for_markets(markets)
|
454
|
-
|
455
|
-
items = self._parse_items_from_json(result)
|
456
|
-
omen_markets = [OmenMarket.model_validate(i) for i in items]
|
435
|
+
omen_markets = self.do_query(fields=fields, pydantic_model=OmenMarket)
|
457
436
|
return omen_markets
|
458
437
|
|
459
438
|
def get_omen_market_by_market_id(self, market_id: HexAddress) -> OmenMarket:
|
@@ -461,7 +440,8 @@ class OmenSubgraphHandler(metaclass=SingletonMeta):
|
|
461
440
|
id=market_id.lower()
|
462
441
|
)
|
463
442
|
|
464
|
-
|
443
|
+
fields = self._get_fields_for_markets(markets)
|
444
|
+
omen_markets = self.do_query(fields=fields, pydantic_model=OmenMarket)
|
465
445
|
|
466
446
|
if len(omen_markets) != 1:
|
467
447
|
raise ValueError(
|
@@ -470,22 +450,6 @@ class OmenSubgraphHandler(metaclass=SingletonMeta):
|
|
470
450
|
|
471
451
|
return omen_markets[0]
|
472
452
|
|
473
|
-
def _parse_items_from_json(
|
474
|
-
self, result: list[dict[str, t.Any]]
|
475
|
-
) -> list[dict[str, t.Any]]:
|
476
|
-
"""subgrounds return a weird key as a dict key"""
|
477
|
-
items = []
|
478
|
-
for result_chunk in result:
|
479
|
-
for k, v in result_chunk.items():
|
480
|
-
# subgrounds might pack all items as a list, indexed by a key, or pack it as a dictionary (if one single element)
|
481
|
-
if v is None:
|
482
|
-
continue
|
483
|
-
elif isinstance(v, dict):
|
484
|
-
items.extend([v])
|
485
|
-
else:
|
486
|
-
items.extend(v)
|
487
|
-
return items
|
488
|
-
|
489
453
|
def _get_fields_for_user_positions(
|
490
454
|
self, user_positions: FieldPath
|
491
455
|
) -> list[FieldPath]:
|
@@ -944,3 +908,17 @@ class OmenSubgraphHandler(metaclass=SingletonMeta):
|
|
944
908
|
if not items:
|
945
909
|
return []
|
946
910
|
return [ContractPrediction.model_validate(i) for i in items]
|
911
|
+
|
912
|
+
def get_agent_results_for_bet(self, bet: OmenBet) -> ContractPrediction | None:
|
913
|
+
results = [
|
914
|
+
result
|
915
|
+
for result in self.get_agent_results_for_market(bet.fpmm.id)
|
916
|
+
if bet.transactionHash in result.tx_hashes
|
917
|
+
]
|
918
|
+
|
919
|
+
if not results:
|
920
|
+
return None
|
921
|
+
elif len(results) > 1:
|
922
|
+
raise RuntimeError("Multiple results found for a single bet.")
|
923
|
+
|
924
|
+
return results[0]
|
@@ -0,0 +1,27 @@
|
|
1
|
+
from pydantic import BaseModel, ConfigDict, Field
|
2
|
+
|
3
|
+
from prediction_market_agent_tooling.gtypes import HexBytes
|
4
|
+
|
5
|
+
|
6
|
+
class SeerMarket(BaseModel):
|
7
|
+
model_config = ConfigDict(populate_by_name=True)
|
8
|
+
|
9
|
+
id: HexBytes
|
10
|
+
title: str = Field(alias="marketName")
|
11
|
+
outcomes: list[str]
|
12
|
+
parent_market: HexBytes = Field(alias="parentMarket")
|
13
|
+
wrapped_tokens: list[HexBytes] = Field(alias="wrappedTokens")
|
14
|
+
|
15
|
+
|
16
|
+
class SeerToken(BaseModel):
|
17
|
+
id: HexBytes
|
18
|
+
name: str
|
19
|
+
symbol: str
|
20
|
+
|
21
|
+
|
22
|
+
class SeerPool(BaseModel):
|
23
|
+
model_config = ConfigDict(populate_by_name=True)
|
24
|
+
id: HexBytes
|
25
|
+
liquidity: int
|
26
|
+
token0: SeerToken
|
27
|
+
token1: SeerToken
|
@@ -0,0 +1,142 @@
|
|
1
|
+
from typing import Any
|
2
|
+
|
3
|
+
from subgrounds import FieldPath
|
4
|
+
from web3.constants import ADDRESS_ZERO
|
5
|
+
|
6
|
+
from prediction_market_agent_tooling.markets.base_subgraph_handler import (
|
7
|
+
BaseSubgraphHandler,
|
8
|
+
)
|
9
|
+
from prediction_market_agent_tooling.markets.seer.data_models import (
|
10
|
+
SeerMarket,
|
11
|
+
SeerPool,
|
12
|
+
)
|
13
|
+
from prediction_market_agent_tooling.tools.hexbytes_custom import HexBytes
|
14
|
+
|
15
|
+
INVALID_OUTCOME = "Invalid result"
|
16
|
+
|
17
|
+
|
18
|
+
class SeerSubgraphHandler(BaseSubgraphHandler):
|
19
|
+
"""
|
20
|
+
Class responsible for handling interactions with Seer subgraphs.
|
21
|
+
"""
|
22
|
+
|
23
|
+
SEER_SUBGRAPH = "https://gateway-arbitrum.network.thegraph.com/api/{graph_api_key}/subgraphs/id/B4vyRqJaSHD8dRDb3BFRoAzuBK18c1QQcXq94JbxDxWH"
|
24
|
+
|
25
|
+
SWAPR_ALGEBRA_SUBGRAPH = "https://gateway-arbitrum.network.thegraph.com/api/{graph_api_key}/subgraphs/id/AAA1vYjxwFHzbt6qKwLHNcDSASyr1J1xVViDH8gTMFMR"
|
26
|
+
|
27
|
+
INVALID_ANSWER = "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"
|
28
|
+
|
29
|
+
def __init__(self) -> None:
|
30
|
+
super().__init__()
|
31
|
+
|
32
|
+
self.seer_subgraph = self.sg.load_subgraph(
|
33
|
+
self.SEER_SUBGRAPH.format(
|
34
|
+
graph_api_key=self.keys.graph_api_key.get_secret_value()
|
35
|
+
)
|
36
|
+
)
|
37
|
+
self.swapr_algebra_subgraph = self.sg.load_subgraph(
|
38
|
+
self.SWAPR_ALGEBRA_SUBGRAPH.format(
|
39
|
+
graph_api_key=self.keys.graph_api_key.get_secret_value()
|
40
|
+
)
|
41
|
+
)
|
42
|
+
|
43
|
+
def _get_fields_for_markets(self, markets_field: FieldPath) -> list[FieldPath]:
|
44
|
+
fields = [
|
45
|
+
markets_field.id,
|
46
|
+
markets_field.factory,
|
47
|
+
markets_field.creator,
|
48
|
+
markets_field.marketName,
|
49
|
+
markets_field.outcomes,
|
50
|
+
markets_field.parentMarket,
|
51
|
+
markets_field.finalizeTs,
|
52
|
+
markets_field.wrappedTokens,
|
53
|
+
]
|
54
|
+
return fields
|
55
|
+
|
56
|
+
@staticmethod
|
57
|
+
def filter_bicategorical_markets(markets: list[SeerMarket]) -> list[SeerMarket]:
|
58
|
+
# We do an extra check for the invalid outcome for safety.
|
59
|
+
return [
|
60
|
+
m for m in markets if len(m.outcomes) == 3 and INVALID_OUTCOME in m.outcomes
|
61
|
+
]
|
62
|
+
|
63
|
+
@staticmethod
|
64
|
+
def filter_binary_markets(markets: list[SeerMarket]) -> list[SeerMarket]:
|
65
|
+
return [
|
66
|
+
market
|
67
|
+
for market in markets
|
68
|
+
if {"yes", "no"}.issubset({o.lower() for o in market.outcomes})
|
69
|
+
]
|
70
|
+
|
71
|
+
@staticmethod
|
72
|
+
def build_filter_for_conditional_markets(
|
73
|
+
include_conditional_markets: bool = True,
|
74
|
+
) -> dict[Any, Any]:
|
75
|
+
return (
|
76
|
+
{}
|
77
|
+
if include_conditional_markets
|
78
|
+
else {"parentMarket": ADDRESS_ZERO.lower()}
|
79
|
+
)
|
80
|
+
|
81
|
+
def get_bicategorical_markets(
|
82
|
+
self, include_conditional_markets: bool = True
|
83
|
+
) -> list[SeerMarket]:
|
84
|
+
"""Returns markets that contain 2 categories plus an invalid outcome."""
|
85
|
+
# Binary markets on Seer contain 3 outcomes: OutcomeA, outcomeB and an Invalid option.
|
86
|
+
query_filter = self.build_filter_for_conditional_markets(
|
87
|
+
include_conditional_markets
|
88
|
+
)
|
89
|
+
query_filter["outcomes_contains"] = [INVALID_OUTCOME]
|
90
|
+
markets_field = self.seer_subgraph.Query.markets(where=query_filter)
|
91
|
+
fields = self._get_fields_for_markets(markets_field)
|
92
|
+
markets = self.do_query(fields=fields, pydantic_model=SeerMarket)
|
93
|
+
two_category_markets = self.filter_bicategorical_markets(markets)
|
94
|
+
return two_category_markets
|
95
|
+
|
96
|
+
def get_binary_markets(
|
97
|
+
self, include_conditional_markets: bool = True
|
98
|
+
) -> list[SeerMarket]:
|
99
|
+
two_category_markets = self.get_bicategorical_markets(
|
100
|
+
include_conditional_markets=include_conditional_markets
|
101
|
+
)
|
102
|
+
# Now we additionally filter markets based on YES/NO being the only outcomes.
|
103
|
+
binary_markets = self.filter_binary_markets(two_category_markets)
|
104
|
+
return binary_markets
|
105
|
+
|
106
|
+
def get_market_by_id(self, market_id: HexBytes) -> SeerMarket:
|
107
|
+
markets_field = self.seer_subgraph.Query.market(id=market_id.hex().lower())
|
108
|
+
fields = self._get_fields_for_markets(markets_field)
|
109
|
+
markets = self.do_query(fields=fields, pydantic_model=SeerMarket)
|
110
|
+
if len(markets) != 1:
|
111
|
+
raise ValueError(
|
112
|
+
f"Fetched wrong number of markets. Expected 1 but got {len(markets)}"
|
113
|
+
)
|
114
|
+
return markets[0]
|
115
|
+
|
116
|
+
def _get_fields_for_pools(self, pools_field: FieldPath) -> list[FieldPath]:
|
117
|
+
fields = [
|
118
|
+
pools_field.id,
|
119
|
+
pools_field.liquidity,
|
120
|
+
pools_field.token0.id,
|
121
|
+
pools_field.token0.name,
|
122
|
+
pools_field.token0.symbol,
|
123
|
+
pools_field.token1.id,
|
124
|
+
pools_field.token1.name,
|
125
|
+
pools_field.token1.symbol,
|
126
|
+
]
|
127
|
+
return fields
|
128
|
+
|
129
|
+
def get_pools_for_market(self, market: SeerMarket) -> list[SeerPool]:
|
130
|
+
# We iterate through the wrapped tokens and put them in a where clause so that we hit the subgraph endpoint just once.
|
131
|
+
wheres = []
|
132
|
+
for wrapped_token in market.wrapped_tokens:
|
133
|
+
wheres.extend(
|
134
|
+
[
|
135
|
+
{"token0": wrapped_token.hex().lower()},
|
136
|
+
{"token1": wrapped_token.hex().lower()},
|
137
|
+
]
|
138
|
+
)
|
139
|
+
pools_field = self.swapr_algebra_subgraph.Query.pools(where={"or": wheres})
|
140
|
+
fields = self._get_fields_for_pools(pools_field)
|
141
|
+
pools = self.do_query(fields=fields, pydantic_model=SeerPool)
|
142
|
+
return pools
|
@@ -15,7 +15,7 @@ from typing import (
|
|
15
15
|
)
|
16
16
|
|
17
17
|
from pydantic import BaseModel
|
18
|
-
from sqlalchemy import Column
|
18
|
+
from sqlalchemy import Column, Engine
|
19
19
|
from sqlalchemy.dialects.postgresql import JSONB
|
20
20
|
from sqlmodel import Field, Session, SQLModel, create_engine, desc, select
|
21
21
|
|
@@ -40,6 +40,9 @@ class FunctionCache(SQLModel, table=True):
|
|
40
40
|
created_at: DatetimeUTC = Field(default_factory=utcnow, index=True)
|
41
41
|
|
42
42
|
|
43
|
+
DB_CACHE_ENGINE: None | Engine = None
|
44
|
+
|
45
|
+
|
43
46
|
@overload
|
44
47
|
def db_cache(
|
45
48
|
func: None = None,
|
@@ -91,40 +94,30 @@ def db_cache(
|
|
91
94
|
|
92
95
|
api_keys = api_keys if api_keys is not None else APIKeys()
|
93
96
|
|
94
|
-
sqlalchemy_db_url = api_keys.SQLALCHEMY_DB_URL
|
95
|
-
if sqlalchemy_db_url is None:
|
96
|
-
logger.warning(
|
97
|
-
f"SQLALCHEMY_DB_URL not provided in the environment, skipping function caching."
|
98
|
-
)
|
99
|
-
|
100
|
-
engine = (
|
101
|
-
create_engine(
|
102
|
-
sqlalchemy_db_url.get_secret_value(),
|
103
|
-
# Use custom json serializer and deserializer, because otherwise, for example `datetime` serialization would fail.
|
104
|
-
json_serializer=json_serializer,
|
105
|
-
json_deserializer=json_deserializer,
|
106
|
-
)
|
107
|
-
if sqlalchemy_db_url is not None
|
108
|
-
else None
|
109
|
-
)
|
110
|
-
|
111
|
-
# Create table if it doesn't exist
|
112
|
-
if engine is not None:
|
113
|
-
SQLModel.metadata.create_all(engine)
|
114
|
-
|
115
97
|
@wraps(func)
|
116
98
|
def wrapper(*args: Any, **kwargs: Any) -> Any:
|
117
99
|
# If caching is disabled, just call the function and return it
|
118
100
|
if not api_keys.ENABLE_CACHE:
|
119
101
|
return func(*args, **kwargs)
|
120
102
|
|
103
|
+
global DB_CACHE_ENGINE
|
104
|
+
if DB_CACHE_ENGINE is None:
|
105
|
+
DB_CACHE_ENGINE = create_engine(
|
106
|
+
api_keys.sqlalchemy_db_url.get_secret_value(),
|
107
|
+
# Use custom json serializer and deserializer, because otherwise, for example `datetime` serialization would fail.
|
108
|
+
json_serializer=json_serializer,
|
109
|
+
json_deserializer=json_deserializer,
|
110
|
+
)
|
111
|
+
# Create table if it doesn't exist
|
112
|
+
SQLModel.metadata.create_all(DB_CACHE_ENGINE)
|
113
|
+
|
121
114
|
# Convert *args and **kwargs to a single dictionary, where we have names for arguments passed as args as well.
|
122
115
|
signature = inspect.signature(func)
|
123
116
|
bound_arguments = signature.bind(*args, **kwargs)
|
124
117
|
bound_arguments.apply_defaults()
|
125
118
|
|
126
119
|
# Convert any argument that is Pydantic model into classic dictionary, otherwise it won't be json-serializable.
|
127
|
-
args_dict =
|
120
|
+
args_dict: dict[str, Any] = bound_arguments.arguments
|
128
121
|
|
129
122
|
# Remove `self` or `cls` if present (in case of class' methods)
|
130
123
|
if "self" in args_dict:
|
@@ -162,25 +155,21 @@ def db_cache(
|
|
162
155
|
if return_type is not None and contains_pydantic_model(return_type):
|
163
156
|
is_pydantic_model = True
|
164
157
|
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
|
171
|
-
.
|
172
|
-
|
173
|
-
FunctionCache.full_function_name == full_function_name,
|
174
|
-
FunctionCache.args_hash == args_hash,
|
175
|
-
)
|
176
|
-
.order_by(desc(FunctionCache.created_at))
|
158
|
+
with Session(DB_CACHE_ENGINE) as session:
|
159
|
+
# Try to get cached result
|
160
|
+
statement = (
|
161
|
+
select(FunctionCache)
|
162
|
+
.where(
|
163
|
+
FunctionCache.function_name == function_name,
|
164
|
+
FunctionCache.full_function_name == full_function_name,
|
165
|
+
FunctionCache.args_hash == args_hash,
|
177
166
|
)
|
178
|
-
|
179
|
-
|
180
|
-
|
181
|
-
|
182
|
-
|
183
|
-
cached_result =
|
167
|
+
.order_by(desc(FunctionCache.created_at))
|
168
|
+
)
|
169
|
+
if max_age is not None:
|
170
|
+
cutoff_time = utcnow() - max_age
|
171
|
+
statement = statement.where(FunctionCache.created_at >= cutoff_time)
|
172
|
+
cached_result = session.exec(statement).first()
|
184
173
|
|
185
174
|
if cached_result:
|
186
175
|
logger.info(
|
@@ -210,22 +199,16 @@ def db_cache(
|
|
210
199
|
)
|
211
200
|
|
212
201
|
# If postgres access was specified, save it.
|
213
|
-
if
|
214
|
-
# In case of Pydantic outputs, we need to dictionarize it for it to be serializable.
|
215
|
-
result_data = (
|
216
|
-
convert_pydantic_to_dict(computed_result)
|
217
|
-
if is_pydantic_model
|
218
|
-
else computed_result
|
219
|
-
)
|
202
|
+
if cache_none or computed_result is not None:
|
220
203
|
cache_entry = FunctionCache(
|
221
204
|
function_name=function_name,
|
222
205
|
full_function_name=full_function_name,
|
223
206
|
args_hash=args_hash,
|
224
207
|
args=args_dict,
|
225
|
-
result=
|
208
|
+
result=computed_result,
|
226
209
|
created_at=utcnow(),
|
227
210
|
)
|
228
|
-
with Session(
|
211
|
+
with Session(DB_CACHE_ENGINE) as session:
|
229
212
|
logger.info(f"Saving {cache_entry} into database.")
|
230
213
|
session.add(cache_entry)
|
231
214
|
session.commit()
|
@@ -249,9 +232,12 @@ def contains_pydantic_model(return_type: Any) -> bool:
|
|
249
232
|
return False
|
250
233
|
|
251
234
|
|
252
|
-
def json_serializer_default_fn(
|
235
|
+
def json_serializer_default_fn(
|
236
|
+
y: DatetimeUTC | timedelta | date | BaseModel,
|
237
|
+
) -> str | dict[str, Any]:
|
253
238
|
"""
|
254
239
|
Used to serialize objects that don't support it by default into a specific string that can be deserialized out later.
|
240
|
+
If this function returns a dictionary, it will be called recursivelly.
|
255
241
|
If you add something here, also add it to `replace_custom_stringified_objects` below.
|
256
242
|
"""
|
257
243
|
if isinstance(y, DatetimeUTC):
|
@@ -260,6 +246,8 @@ def json_serializer_default_fn(y: DatetimeUTC | timedelta | date) -> str:
|
|
260
246
|
return f"timedelta::{y.total_seconds()}"
|
261
247
|
elif isinstance(y, date):
|
262
248
|
return f"date::{y.isoformat()}"
|
249
|
+
elif isinstance(y, BaseModel):
|
250
|
+
return y.model_dump()
|
263
251
|
raise TypeError(
|
264
252
|
f"Unsuported type for the default json serialize function, value is {y}."
|
265
253
|
)
|
@@ -298,22 +286,6 @@ def json_deserializer(s: str) -> Any:
|
|
298
286
|
return replace_custom_stringified_objects(data)
|
299
287
|
|
300
288
|
|
301
|
-
def convert_pydantic_to_dict(value: Any) -> Any:
|
302
|
-
"""
|
303
|
-
Convert Pydantic models to dictionaries, including if they are in nested structures.
|
304
|
-
"""
|
305
|
-
if isinstance(value, BaseModel):
|
306
|
-
return value.model_dump()
|
307
|
-
elif isinstance(value, dict):
|
308
|
-
return {k: convert_pydantic_to_dict(v) for k, v in value.items()}
|
309
|
-
elif isinstance(value, (list, tuple)):
|
310
|
-
return type(value)(convert_pydantic_to_dict(v) for v in value)
|
311
|
-
elif isinstance(value, set):
|
312
|
-
return {convert_pydantic_to_dict(v) for v in value}
|
313
|
-
else:
|
314
|
-
return value
|
315
|
-
|
316
|
-
|
317
289
|
def convert_cached_output_to_pydantic(return_type: Any, data: Any) -> Any:
|
318
290
|
"""
|
319
291
|
Used to initialize Pydantic models from anything cached that was originally a Pydantic model in the output. Including models in nested structures.
|
@@ -34,7 +34,7 @@ QUESTION_IS_INVALID_PROMPT = """Main signs about an invalid question (sometimes
|
|
34
34
|
- Which could give an incentive only to specific participants to commit an immoral violent action, but are in practice unlikely.
|
35
35
|
- Valid: Will the US be engaged in a military conflict with a UN member state in 2021? (It’s unlikely for the US to declare war in order to win a bet on this market).
|
36
36
|
- Valid: Will Derek Chauvin go to jail for the murder of George Flyod? (It’s unlikely that the jurors would collude to make a wrong verdict in order to win this market).
|
37
|
-
- Questions with relative dates will resolve as invalid. Dates must be stated in absolute terms, not relative depending on the current time. But they can be relative to the
|
37
|
+
- Questions with relative dates will resolve as invalid. Dates must be stated in absolute terms, not relative depending on the current time. But they can be relative to the event specified in the question itself.
|
38
38
|
- Invalid: Who will be the president of the United States in 6 months? ("in 6 months depends on the current time").
|
39
39
|
- Invalid: In the next 14 days, will Gnosis Chain gain another 1M users? ("in the next 14 days depends on the current time").
|
40
40
|
- Valid: Will GNO price go up 10 days after Gnosis Pay cashback program is annouced? ("10 days after" is relative to the event in the question, so we can determine absolute value).
|
@@ -16,8 +16,8 @@ prediction_market_agent_tooling/benchmark/__init__.py,sha256=47DEQpj8HBSa-_TImW-
|
|
16
16
|
prediction_market_agent_tooling/benchmark/agents.py,sha256=B1-uWdyeN4GGKMWGK_-CcAFJg1m9Y_XuaeIHPB29QR8,3971
|
17
17
|
prediction_market_agent_tooling/benchmark/benchmark.py,sha256=MqTiaaJ3cYiOLUVR7OyImLWxcEya3Rl5JyFYW-K0lwM,17097
|
18
18
|
prediction_market_agent_tooling/benchmark/utils.py,sha256=D0MfUkVZllmvcU0VOurk9tcKT7JTtwwOp-63zuCBVuc,2880
|
19
|
-
prediction_market_agent_tooling/config.py,sha256=
|
20
|
-
prediction_market_agent_tooling/deploy/agent.py,sha256=
|
19
|
+
prediction_market_agent_tooling/config.py,sha256=6iB8A4byexiemUajIw2SzRDQ8E8w0catyg7aH_mHpsY,6732
|
20
|
+
prediction_market_agent_tooling/deploy/agent.py,sha256=dpc94DUo8Gq1LdRdw6k78vm_47OeJIfomG9CRVpgzk0,22757
|
21
21
|
prediction_market_agent_tooling/deploy/agent_example.py,sha256=dIIdZashExWk9tOdyDjw87AuUcGyM7jYxNChYrVK2dM,1001
|
22
22
|
prediction_market_agent_tooling/deploy/betting_strategy.py,sha256=kMrIE3wMv_IB6nJd_1DmDXDkEZhsXFOgyTd7JZ0gqHI,13068
|
23
23
|
prediction_market_agent_tooling/deploy/constants.py,sha256=M5ty8URipYMGe_G-RzxRydK3AFL6CyvmqCraJUrLBnE,82
|
@@ -27,11 +27,11 @@ prediction_market_agent_tooling/deploy/gcp/utils.py,sha256=oyW0jgrUT2Tr49c7GlpcM
|
|
27
27
|
prediction_market_agent_tooling/deploy/trade_interval.py,sha256=Xk9j45alQ_vrasGvsNyuW70XHIQ7wfvjoxNR3F6HYCw,1155
|
28
28
|
prediction_market_agent_tooling/gtypes.py,sha256=tqp03PyY0Yhievl4XELfwAn0xOoecaTvBZ1Co6b-A7o,2541
|
29
29
|
prediction_market_agent_tooling/jobs/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
30
|
-
prediction_market_agent_tooling/jobs/
|
31
|
-
prediction_market_agent_tooling/jobs/
|
32
|
-
prediction_market_agent_tooling/jobs/omen/omen_jobs.py,sha256=I2_vGrEJj1reSI8M377ab5QCsYNp_l4l4QeYEmDBkFM,3989
|
30
|
+
prediction_market_agent_tooling/jobs/jobs_models.py,sha256=GOtsNm7URhzZM5fPY64r8m8Gz-sSsUhG1qmDoC7wGL8,2231
|
31
|
+
prediction_market_agent_tooling/jobs/omen/omen_jobs.py,sha256=N0_jGDyXQeVXXlYg4oA_pOfqIjscHsLQbr0pBwFGoRo,5178
|
33
32
|
prediction_market_agent_tooling/loggers.py,sha256=Am6HHXRNO545BO3l7Ue9Wb2TkYE1OK8KKhGbI3XypVU,3751
|
34
|
-
prediction_market_agent_tooling/markets/agent_market.py,sha256=
|
33
|
+
prediction_market_agent_tooling/markets/agent_market.py,sha256=W2ME57-CSAhrt8qm8-b5r7yLq-Sk7R_BZMaApvjhrUE,12901
|
34
|
+
prediction_market_agent_tooling/markets/base_subgraph_handler.py,sha256=IxDTwX4tej9j5olNkXcLIE0RCF1Nh2icZQUT2ERMmZo,1937
|
35
35
|
prediction_market_agent_tooling/markets/categorize.py,sha256=jsoHWvZk9pU6n17oWSCcCxNNYVwlb_NXsZxKRI7vmsk,1301
|
36
36
|
prediction_market_agent_tooling/markets/data_models.py,sha256=jMqrSFO_w2z-5N3PFVgZqTHdVdkzSDhhzky2lHsGGKA,3621
|
37
37
|
prediction_market_agent_tooling/markets/manifold/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
@@ -40,21 +40,23 @@ prediction_market_agent_tooling/markets/manifold/data_models.py,sha256=ylXIEHymx
|
|
40
40
|
prediction_market_agent_tooling/markets/manifold/manifold.py,sha256=qemQIwuFg4yf6egGWFp9lWpz1lXr02QiBeZ2akcT6II,5026
|
41
41
|
prediction_market_agent_tooling/markets/manifold/utils.py,sha256=cPPFWXm3vCYH1jy7_ctJZuQH9ZDaPL4_AgAYzGWkoow,513
|
42
42
|
prediction_market_agent_tooling/markets/market_fees.py,sha256=Q64T9uaJx0Vllt0BkrPmpMEz53ra-hMVY8Czi7CEP7s,1227
|
43
|
-
prediction_market_agent_tooling/markets/markets.py,sha256=
|
43
|
+
prediction_market_agent_tooling/markets/markets.py,sha256=_b-BAfoKIcXl5ZXVODi1ywMhRCbc52022csH1nQT084,3893
|
44
44
|
prediction_market_agent_tooling/markets/metaculus/api.py,sha256=4TRPGytQQbSdf42DCg2M_JWYPAuNjqZ3eBqaQBLkNks,2736
|
45
45
|
prediction_market_agent_tooling/markets/metaculus/data_models.py,sha256=Suxa7xELdYuFNKqvGvFh8qyfVtAg79E-vaQ6dqNZOtA,3261
|
46
|
-
prediction_market_agent_tooling/markets/metaculus/metaculus.py,sha256=
|
46
|
+
prediction_market_agent_tooling/markets/metaculus/metaculus.py,sha256=86TIx6cavEWc8Cv4KpZxSvwiSw9oFybXE3YB49pg-CA,4377
|
47
47
|
prediction_market_agent_tooling/markets/omen/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
48
|
-
prediction_market_agent_tooling/markets/omen/data_models.py,sha256=
|
49
|
-
prediction_market_agent_tooling/markets/omen/omen.py,sha256=
|
48
|
+
prediction_market_agent_tooling/markets/omen/data_models.py,sha256=0eky-RO0TuJysUXHd52A6DSU2mx1ZWiJvvntS4xQsUc,27794
|
49
|
+
prediction_market_agent_tooling/markets/omen/omen.py,sha256=uOuV2DgQmxz6kzPcMovyGg0xYS0c6x12fEFNtLmN-uY,51260
|
50
50
|
prediction_market_agent_tooling/markets/omen/omen_contracts.py,sha256=Zq7SncCq-hvpgXKsVruGBGCn1OhKZTe7r1qLdCTrT2w,28297
|
51
51
|
prediction_market_agent_tooling/markets/omen/omen_resolving.py,sha256=iDWdjICGkt968exwCjY-6nsnQyrrNAg3YjnDdP430GQ,9415
|
52
|
-
prediction_market_agent_tooling/markets/omen/omen_subgraph_handler.py,sha256=
|
52
|
+
prediction_market_agent_tooling/markets/omen/omen_subgraph_handler.py,sha256=cXgtBfc5uv7d8598SJ537LxFGVfF_mv-VoQoqI4_G84,36330
|
53
53
|
prediction_market_agent_tooling/markets/polymarket/api.py,sha256=UZ4_TG8ceb9Y-qgsOKs8Qiv8zDt957QkT8IX2c83yqo,4800
|
54
54
|
prediction_market_agent_tooling/markets/polymarket/data_models.py,sha256=Fd5PI5y3mJM8VHExBhWFWEnuuIKxQmIAXgBuoPDvNjw,4341
|
55
55
|
prediction_market_agent_tooling/markets/polymarket/data_models_web.py,sha256=VZhVccTApygSKMmy6Au2G02JCJOKJnR_oVeKlaesuSg,12548
|
56
56
|
prediction_market_agent_tooling/markets/polymarket/polymarket.py,sha256=NRoZK71PtH8kkangMqme7twcAXhRJSSabbmOir-UnAI,3418
|
57
57
|
prediction_market_agent_tooling/markets/polymarket/utils.py,sha256=m4JG6WULh5epCJt4XBMHg0ae5NoVhqlOvAl0A7DR9iM,2023
|
58
|
+
prediction_market_agent_tooling/markets/seer/data_models.py,sha256=2f6DOXhGerYbTSk5vUvw_y87TcUxOtSfca8-Et7imtU,643
|
59
|
+
prediction_market_agent_tooling/markets/seer/seer_subgraph_handler.py,sha256=Jola8AxmNDljBCzl6Gvjb9qH75Y7D5dAwbdZl_jndN8,5478
|
58
60
|
prediction_market_agent_tooling/monitor/markets/manifold.py,sha256=TS4ERwTfQnot8dhekNyVNhJYf5ysYsjF-9v5_kM3aVI,3334
|
59
61
|
prediction_market_agent_tooling/monitor/markets/metaculus.py,sha256=LOnyWWBFdg10-cTWdb76nOsNjDloO8OfMT85GBzRCFI,1455
|
60
62
|
prediction_market_agent_tooling/monitor/markets/omen.py,sha256=EqiJYTvDbSu7fBpbrBmCuf3fc6GHr4MxWrBGa69MIyc,3305
|
@@ -69,7 +71,7 @@ prediction_market_agent_tooling/tools/betting_strategies/market_moving.py,sha256
|
|
69
71
|
prediction_market_agent_tooling/tools/betting_strategies/minimum_bet_to_win.py,sha256=-FUSuQQgjcWSSnoFxnlAyTeilY6raJABJVM2QKkFqAY,438
|
70
72
|
prediction_market_agent_tooling/tools/betting_strategies/stretch_bet_between.py,sha256=THMXwFlskvzbjnX_OiYtDSzI8XVFyULWfP2525_9UGc,429
|
71
73
|
prediction_market_agent_tooling/tools/betting_strategies/utils.py,sha256=kpIb-ci67Vc1Yqqaa-_S4OUkbhWSIYog4_Iwp69HU_k,97
|
72
|
-
prediction_market_agent_tooling/tools/caches/db_cache.py,sha256=
|
74
|
+
prediction_market_agent_tooling/tools/caches/db_cache.py,sha256=s8MKNWa3EFIRMZbi5YqgOd4S1boMPkUrGFgtW8qBNlw,12844
|
73
75
|
prediction_market_agent_tooling/tools/caches/inmemory_cache.py,sha256=tGHHd9HCiE_hCCtPtloHZQdDfBuiow9YsqJNYi2Tx_0,499
|
74
76
|
prediction_market_agent_tooling/tools/contract.py,sha256=s3yo8IbXTcvAJcPfLM0_NbgaEsWwLsPmyVnOgyjq_xI,20919
|
75
77
|
prediction_market_agent_tooling/tools/costs.py,sha256=EaAJ7v9laD4VEV3d8B44M4u3_oEO_H16jRVCdoZ93Uw,954
|
@@ -81,7 +83,7 @@ prediction_market_agent_tooling/tools/httpx_cached_client.py,sha256=0-N1r0zcGKlY
|
|
81
83
|
prediction_market_agent_tooling/tools/image_gen/image_gen.py,sha256=HzRwBx62hOXBOmrtpkXaP9Qq1Ku03uUGdREocyjLQ_k,1266
|
82
84
|
prediction_market_agent_tooling/tools/image_gen/market_thumbnail_gen.py,sha256=8A3U2uxsCsOfLjru-6R_PPIAuiKY4qFkWp_GSBPV6-s,1280
|
83
85
|
prediction_market_agent_tooling/tools/ipfs/ipfs_handler.py,sha256=CTTMfTvs_8PH4kAtlQby2aeEKwgpmxtuGbd4oYIdJ2A,1201
|
84
|
-
prediction_market_agent_tooling/tools/is_invalid.py,sha256=
|
86
|
+
prediction_market_agent_tooling/tools/is_invalid.py,sha256=GSMwSWUZy-xviaFoIl0L34AVfLLTdh7zegjsTFE7_1M,5323
|
85
87
|
prediction_market_agent_tooling/tools/is_predictable.py,sha256=VGkxSoJ8CSLknloOLzm5J4-us7XImYxVzvpsAzxbpCc,6730
|
86
88
|
prediction_market_agent_tooling/tools/langfuse_.py,sha256=jI_4ROxqo41CCnWGS1vN_AeDVhRzLMaQLxH3kxDu3L8,1153
|
87
89
|
prediction_market_agent_tooling/tools/langfuse_client_utils.py,sha256=B0PhAQyviFnVbtOCYMxYmcCn66cu9nbqAOIAZcdgiRI,5771
|
@@ -95,10 +97,10 @@ prediction_market_agent_tooling/tools/singleton.py,sha256=CiIELUiI-OeS7U7eeHEt0r
|
|
95
97
|
prediction_market_agent_tooling/tools/streamlit_user_login.py,sha256=NXEqfjT9Lc9QtliwSGRASIz1opjQ7Btme43H4qJbzgE,3010
|
96
98
|
prediction_market_agent_tooling/tools/tavily/tavily_models.py,sha256=5ldQs1pZe6uJ5eDAuP4OLpzmcqYShlIV67kttNFvGS0,342
|
97
99
|
prediction_market_agent_tooling/tools/tavily/tavily_search.py,sha256=Kw2mXNkMTYTEe1MBSTqhQmLoeXtgb6CkmHlcAJvhtqE,3809
|
98
|
-
prediction_market_agent_tooling/tools/utils.py,sha256=
|
100
|
+
prediction_market_agent_tooling/tools/utils.py,sha256=1VvunbTmzGzpIlRukFhArreFNxJPbsg4lLtQNk0r2bY,7185
|
99
101
|
prediction_market_agent_tooling/tools/web3_utils.py,sha256=44W8siSLNQxeib98bbwAe7V5C609NHNlUuxwuWIRDiY,11838
|
100
|
-
prediction_market_agent_tooling-0.56.
|
101
|
-
prediction_market_agent_tooling-0.56.
|
102
|
-
prediction_market_agent_tooling-0.56.
|
103
|
-
prediction_market_agent_tooling-0.56.
|
104
|
-
prediction_market_agent_tooling-0.56.
|
102
|
+
prediction_market_agent_tooling-0.56.2.dev129.dist-info/LICENSE,sha256=6or154nLLU6bELzjh0mCreFjt0m2v72zLi3yHE0QbeE,7650
|
103
|
+
prediction_market_agent_tooling-0.56.2.dev129.dist-info/METADATA,sha256=uWLV8rw76xcE9yFjv1CYRp3mpM_HbYBFho1qpIyeoCc,8113
|
104
|
+
prediction_market_agent_tooling-0.56.2.dev129.dist-info/WHEEL,sha256=Nq82e9rUAnEjt98J6MlVmMCZb-t9cYE2Ir1kpBmnWfs,88
|
105
|
+
prediction_market_agent_tooling-0.56.2.dev129.dist-info/entry_points.txt,sha256=m8PukHbeH5g0IAAmOf_1Ahm-sGAMdhSSRQmwtpmi2s8,81
|
106
|
+
prediction_market_agent_tooling-0.56.2.dev129.dist-info/RECORD,,
|
@@ -1,45 +0,0 @@
|
|
1
|
-
import typing as t
|
2
|
-
|
3
|
-
from prediction_market_agent_tooling.jobs.jobs_models import JobAgentMarket
|
4
|
-
from prediction_market_agent_tooling.jobs.omen.omen_jobs import OmenJobAgentMarket
|
5
|
-
from prediction_market_agent_tooling.markets.agent_market import FilterBy, SortBy
|
6
|
-
from prediction_market_agent_tooling.markets.markets import MarketType
|
7
|
-
|
8
|
-
JOB_MARKET_TYPE_TO_JOB_AGENT_MARKET: dict[MarketType, type[JobAgentMarket]] = {
|
9
|
-
MarketType.OMEN: OmenJobAgentMarket,
|
10
|
-
}
|
11
|
-
|
12
|
-
|
13
|
-
@t.overload
|
14
|
-
def get_jobs(
|
15
|
-
market_type: t.Literal[MarketType.OMEN],
|
16
|
-
limit: int | None,
|
17
|
-
filter_by: FilterBy = FilterBy.OPEN,
|
18
|
-
sort_by: SortBy = SortBy.NONE,
|
19
|
-
) -> t.Sequence[OmenJobAgentMarket]:
|
20
|
-
...
|
21
|
-
|
22
|
-
|
23
|
-
@t.overload
|
24
|
-
def get_jobs(
|
25
|
-
market_type: MarketType,
|
26
|
-
limit: int | None,
|
27
|
-
filter_by: FilterBy = FilterBy.OPEN,
|
28
|
-
sort_by: SortBy = SortBy.NONE,
|
29
|
-
) -> t.Sequence[JobAgentMarket]:
|
30
|
-
...
|
31
|
-
|
32
|
-
|
33
|
-
def get_jobs(
|
34
|
-
market_type: MarketType,
|
35
|
-
limit: int | None,
|
36
|
-
filter_by: FilterBy = FilterBy.OPEN,
|
37
|
-
sort_by: SortBy = SortBy.NONE,
|
38
|
-
) -> t.Sequence[JobAgentMarket]:
|
39
|
-
job_class = JOB_MARKET_TYPE_TO_JOB_AGENT_MARKET[market_type]
|
40
|
-
markets = job_class.get_jobs(
|
41
|
-
limit=limit,
|
42
|
-
sort_by=sort_by,
|
43
|
-
filter_by=filter_by,
|
44
|
-
)
|
45
|
-
return markets
|
File without changes
|
File without changes
|