prediction-market-agent-tooling 0.48.13__py3-none-any.whl → 0.48.15__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- prediction_market_agent_tooling/deploy/agent.py +9 -0
- prediction_market_agent_tooling/deploy/betting_strategy.py +23 -6
- prediction_market_agent_tooling/gtypes.py +1 -1
- prediction_market_agent_tooling/jobs/__init__.py +0 -0
- prediction_market_agent_tooling/jobs/jobs.py +45 -0
- prediction_market_agent_tooling/jobs/jobs_models.py +53 -0
- prediction_market_agent_tooling/jobs/omen/omen_jobs.py +113 -0
- prediction_market_agent_tooling/markets/omen/data_models.py +3 -0
- prediction_market_agent_tooling/markets/omen/omen.py +97 -8
- prediction_market_agent_tooling/markets/omen/omen_subgraph_handler.py +17 -8
- prediction_market_agent_tooling/monitor/monitor.py +5 -1
- prediction_market_agent_tooling/tools/betting_strategies/kelly_criterion.py +91 -9
- prediction_market_agent_tooling/tools/betting_strategies/market_moving.py +83 -67
- prediction_market_agent_tooling/tools/betting_strategies/utils.py +6 -0
- prediction_market_agent_tooling/tools/is_predictable.py +3 -0
- prediction_market_agent_tooling/tools/langfuse_client_utils.py +159 -0
- {prediction_market_agent_tooling-0.48.13.dist-info → prediction_market_agent_tooling-0.48.15.dist-info}/METADATA +1 -1
- {prediction_market_agent_tooling-0.48.13.dist-info → prediction_market_agent_tooling-0.48.15.dist-info}/RECORD +21 -15
- {prediction_market_agent_tooling-0.48.13.dist-info → prediction_market_agent_tooling-0.48.15.dist-info}/LICENSE +0 -0
- {prediction_market_agent_tooling-0.48.13.dist-info → prediction_market_agent_tooling-0.48.15.dist-info}/WHEEL +0 -0
- {prediction_market_agent_tooling-0.48.13.dist-info → prediction_market_agent_tooling-0.48.15.dist-info}/entry_points.txt +0 -0
@@ -426,6 +426,8 @@ class DeployableTraderAgent(DeployableAgent):
|
|
426
426
|
market: AgentMarket,
|
427
427
|
verify_market: bool = True,
|
428
428
|
) -> ProcessedMarket | None:
|
429
|
+
logger.info(f"Processing market {market.question=} from {market.url=}.")
|
430
|
+
|
429
431
|
self.before_process_market(market_type, market)
|
430
432
|
|
431
433
|
if verify_market and not self.verify_market(market_type, market):
|
@@ -462,6 +464,7 @@ class DeployableTraderAgent(DeployableAgent):
|
|
462
464
|
processed_market = ProcessedMarket(answer=answer, trades=trades)
|
463
465
|
self.update_langfuse_trace_by_processed_market(market_type, processed_market)
|
464
466
|
|
467
|
+
logger.info(f"Processed market {market.question=} from {market.url=}.")
|
465
468
|
return processed_market
|
466
469
|
|
467
470
|
def after_process_market(
|
@@ -495,7 +498,11 @@ class DeployableTraderAgent(DeployableAgent):
|
|
495
498
|
"""
|
496
499
|
Processes bets placed by agents on a given market.
|
497
500
|
"""
|
501
|
+
logger.info("Start processing of markets.")
|
498
502
|
available_markets = self.get_markets(market_type)
|
503
|
+
logger.info(
|
504
|
+
f"Fetched {len(available_markets)=} markets to process, going to process {self.bet_on_n_markets_per_run=}."
|
505
|
+
)
|
499
506
|
processed = 0
|
500
507
|
|
501
508
|
for market in available_markets:
|
@@ -510,6 +517,8 @@ class DeployableTraderAgent(DeployableAgent):
|
|
510
517
|
if processed == self.bet_on_n_markets_per_run:
|
511
518
|
break
|
512
519
|
|
520
|
+
logger.info("All markets processed.")
|
521
|
+
|
513
522
|
def after_process_markets(self, market_type: MarketType) -> None:
|
514
523
|
pass
|
515
524
|
|
@@ -11,8 +11,10 @@ from prediction_market_agent_tooling.markets.data_models import (
|
|
11
11
|
)
|
12
12
|
from prediction_market_agent_tooling.markets.omen.data_models import get_boolean_outcome
|
13
13
|
from prediction_market_agent_tooling.tools.betting_strategies.kelly_criterion import (
|
14
|
-
|
14
|
+
get_kelly_bet_full,
|
15
|
+
get_kelly_bet_simplified,
|
15
16
|
)
|
17
|
+
from prediction_market_agent_tooling.tools.utils import check_not_none
|
16
18
|
|
17
19
|
|
18
20
|
class BettingStrategy(ABC):
|
@@ -168,11 +170,26 @@ class KellyBettingStrategy(BettingStrategy):
|
|
168
170
|
market: AgentMarket,
|
169
171
|
) -> list[Trade]:
|
170
172
|
adjusted_bet_amount = self.adjust_bet_amount(existing_position, market)
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
|
173
|
+
outcome_token_pool = check_not_none(market.outcome_token_pool)
|
174
|
+
kelly_bet = (
|
175
|
+
get_kelly_bet_full(
|
176
|
+
yes_outcome_pool_size=outcome_token_pool[
|
177
|
+
market.get_outcome_str_from_bool(True)
|
178
|
+
],
|
179
|
+
no_outcome_pool_size=outcome_token_pool[
|
180
|
+
market.get_outcome_str_from_bool(False)
|
181
|
+
],
|
182
|
+
estimated_p_yes=answer.p_yes,
|
183
|
+
max_bet=adjusted_bet_amount,
|
184
|
+
confidence=answer.confidence,
|
185
|
+
)
|
186
|
+
if market.has_token_pool()
|
187
|
+
else get_kelly_bet_simplified(
|
188
|
+
adjusted_bet_amount,
|
189
|
+
market.current_p_yes,
|
190
|
+
answer.p_yes,
|
191
|
+
answer.confidence,
|
192
|
+
)
|
176
193
|
)
|
177
194
|
|
178
195
|
amounts = {
|
@@ -27,7 +27,7 @@ PrivateKey = NewType("PrivateKey", SecretStr)
|
|
27
27
|
xDai = NewType("xDai", float)
|
28
28
|
GNO = NewType("GNO", float)
|
29
29
|
ABI = NewType("ABI", str)
|
30
|
-
OmenOutcomeToken = NewType("OmenOutcomeToken",
|
30
|
+
OmenOutcomeToken = NewType("OmenOutcomeToken", Wei)
|
31
31
|
OutcomeStr = NewType("OutcomeStr", str)
|
32
32
|
Probability = NewType("Probability", float)
|
33
33
|
Mana = NewType("Mana", float) # Manifold's "currency"
|
File without changes
|
@@ -0,0 +1,45 @@
|
|
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
|
@@ -0,0 +1,53 @@
|
|
1
|
+
import typing as t
|
2
|
+
from abc import ABC, abstractmethod
|
3
|
+
from datetime import datetime
|
4
|
+
|
5
|
+
from pydantic import BaseModel
|
6
|
+
|
7
|
+
from prediction_market_agent_tooling.markets.agent_market import AgentMarket
|
8
|
+
from prediction_market_agent_tooling.markets.omen.omen_subgraph_handler import (
|
9
|
+
FilterBy,
|
10
|
+
SortBy,
|
11
|
+
)
|
12
|
+
|
13
|
+
|
14
|
+
class SimpleJob(BaseModel):
|
15
|
+
id: str
|
16
|
+
job: str
|
17
|
+
reward: float
|
18
|
+
currency: str
|
19
|
+
deadline: datetime
|
20
|
+
|
21
|
+
|
22
|
+
class JobAgentMarket(AgentMarket, ABC):
|
23
|
+
CATEGORY: t.ClassVar[str]
|
24
|
+
|
25
|
+
@property
|
26
|
+
@abstractmethod
|
27
|
+
def job(self) -> str:
|
28
|
+
"""Holds description of the job that needs to be done."""
|
29
|
+
|
30
|
+
@property
|
31
|
+
@abstractmethod
|
32
|
+
def deadline(self) -> datetime:
|
33
|
+
"""Deadline for the job completion."""
|
34
|
+
|
35
|
+
@abstractmethod
|
36
|
+
def get_reward(self, max_bond: float) -> float:
|
37
|
+
"""Reward for completing this job."""
|
38
|
+
|
39
|
+
@abstractmethod
|
40
|
+
@classmethod
|
41
|
+
def get_jobs(
|
42
|
+
cls, limit: int | None, filter_by: FilterBy, sort_by: SortBy
|
43
|
+
) -> t.Sequence["JobAgentMarket"]:
|
44
|
+
"""Get all available jobs."""
|
45
|
+
|
46
|
+
def to_simple_job(self, max_bond: float) -> SimpleJob:
|
47
|
+
return SimpleJob(
|
48
|
+
id=self.id,
|
49
|
+
job=self.job,
|
50
|
+
reward=self.get_reward(max_bond),
|
51
|
+
currency=self.currency.value,
|
52
|
+
deadline=self.deadline,
|
53
|
+
)
|
@@ -0,0 +1,113 @@
|
|
1
|
+
import typing as t
|
2
|
+
from datetime import datetime
|
3
|
+
|
4
|
+
from web3 import Web3
|
5
|
+
|
6
|
+
from prediction_market_agent_tooling.deploy.betting_strategy import (
|
7
|
+
Currency,
|
8
|
+
KellyBettingStrategy,
|
9
|
+
ProbabilisticAnswer,
|
10
|
+
TradeType,
|
11
|
+
)
|
12
|
+
from prediction_market_agent_tooling.gtypes import Probability
|
13
|
+
from prediction_market_agent_tooling.jobs.jobs_models import JobAgentMarket
|
14
|
+
from prediction_market_agent_tooling.markets.omen.omen import (
|
15
|
+
BetAmount,
|
16
|
+
OmenAgentMarket,
|
17
|
+
OmenMarket,
|
18
|
+
)
|
19
|
+
from prediction_market_agent_tooling.markets.omen.omen_subgraph_handler import (
|
20
|
+
FilterBy,
|
21
|
+
OmenSubgraphHandler,
|
22
|
+
SortBy,
|
23
|
+
)
|
24
|
+
|
25
|
+
|
26
|
+
class OmenJobAgentMarket(OmenAgentMarket, JobAgentMarket):
|
27
|
+
CATEGORY = "jobs"
|
28
|
+
|
29
|
+
@property
|
30
|
+
def job(self) -> str:
|
31
|
+
"""Omen market's have only question, so that's where the job description is."""
|
32
|
+
return self.question
|
33
|
+
|
34
|
+
@property
|
35
|
+
def deadline(self) -> datetime:
|
36
|
+
return self.close_time
|
37
|
+
|
38
|
+
def get_reward(self, max_bond: float) -> float:
|
39
|
+
return compute_job_reward(self, max_bond)
|
40
|
+
|
41
|
+
@classmethod
|
42
|
+
def get_jobs(
|
43
|
+
cls, limit: int | None, filter_by: FilterBy, sort_by: SortBy
|
44
|
+
) -> t.Sequence["OmenJobAgentMarket"]:
|
45
|
+
markets = OmenSubgraphHandler().get_omen_binary_markets_simple(
|
46
|
+
limit=limit,
|
47
|
+
filter_by=filter_by,
|
48
|
+
sort_by=sort_by,
|
49
|
+
category=cls.CATEGORY,
|
50
|
+
)
|
51
|
+
return [OmenJobAgentMarket.from_omen_market(market) for market in markets]
|
52
|
+
|
53
|
+
@staticmethod
|
54
|
+
def from_omen_market(market: OmenMarket) -> "OmenJobAgentMarket":
|
55
|
+
return OmenJobAgentMarket.from_omen_agent_market(
|
56
|
+
OmenAgentMarket.from_data_model(market)
|
57
|
+
)
|
58
|
+
|
59
|
+
@staticmethod
|
60
|
+
def from_omen_agent_market(market: OmenAgentMarket) -> "OmenJobAgentMarket":
|
61
|
+
return OmenJobAgentMarket(
|
62
|
+
id=market.id,
|
63
|
+
question=market.question,
|
64
|
+
description=market.description,
|
65
|
+
outcomes=market.outcomes,
|
66
|
+
outcome_token_pool=market.outcome_token_pool,
|
67
|
+
resolution=market.resolution,
|
68
|
+
created_time=market.created_time,
|
69
|
+
close_time=market.close_time,
|
70
|
+
current_p_yes=market.current_p_yes,
|
71
|
+
url=market.url,
|
72
|
+
volume=market.volume,
|
73
|
+
creator=market.creator,
|
74
|
+
collateral_token_contract_address_checksummed=market.collateral_token_contract_address_checksummed,
|
75
|
+
market_maker_contract_address_checksummed=market.market_maker_contract_address_checksummed,
|
76
|
+
condition=market.condition,
|
77
|
+
finalized_time=market.finalized_time,
|
78
|
+
fee=market.fee,
|
79
|
+
)
|
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
|
+
required_trades = KellyBettingStrategy(max_bet_amount=max_bond).calculate_trades(
|
87
|
+
existing_position=None,
|
88
|
+
# We assume that we finish the job and so the probability of the market happening will be 100%.
|
89
|
+
answer=ProbabilisticAnswer(p_yes=Probability(1.0), confidence=1.0),
|
90
|
+
market=market,
|
91
|
+
)
|
92
|
+
|
93
|
+
assert (
|
94
|
+
len(required_trades) == 1
|
95
|
+
), f"Shouldn't process same job twice: {required_trades}"
|
96
|
+
trade = required_trades[0]
|
97
|
+
assert trade.trade_type == TradeType.BUY, "Should only buy on job markets."
|
98
|
+
assert trade.outcome, "Should buy only YES on job markets."
|
99
|
+
assert (
|
100
|
+
trade.amount.currency == Currency.xDai
|
101
|
+
), "Should work only on real-money markets."
|
102
|
+
|
103
|
+
reward = (
|
104
|
+
market.get_buy_token_amount(
|
105
|
+
bet_amount=BetAmount(
|
106
|
+
amount=trade.amount.amount, currency=trade.amount.currency
|
107
|
+
),
|
108
|
+
direction=trade.outcome,
|
109
|
+
).amount
|
110
|
+
- trade.amount.amount
|
111
|
+
)
|
112
|
+
|
113
|
+
return reward
|
@@ -9,6 +9,7 @@ from prediction_market_agent_tooling.gtypes import (
|
|
9
9
|
ChecksumAddress,
|
10
10
|
HexAddress,
|
11
11
|
HexBytes,
|
12
|
+
HexStr,
|
12
13
|
OmenOutcomeToken,
|
13
14
|
Probability,
|
14
15
|
Wei,
|
@@ -30,8 +31,10 @@ from prediction_market_agent_tooling.tools.web3_utils import wei_to_xdai
|
|
30
31
|
|
31
32
|
OMEN_TRUE_OUTCOME = "Yes"
|
32
33
|
OMEN_FALSE_OUTCOME = "No"
|
34
|
+
OMEN_BINARY_MARKET_OUTCOMES = [OMEN_TRUE_OUTCOME, OMEN_FALSE_OUTCOME]
|
33
35
|
INVALID_ANSWER = 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF
|
34
36
|
INVALID_ANSWER_HEX_BYTES = HexBytes(INVALID_ANSWER)
|
37
|
+
INVALID_ANSWER_STR = HexStr(INVALID_ANSWER_HEX_BYTES.hex())
|
35
38
|
OMEN_BASE_URL = "https://aiomen.eth.limo"
|
36
39
|
PRESAGIO_BASE_URL = "https://presagio.pages.dev"
|
37
40
|
|
@@ -97,10 +97,6 @@ class OmenAgentMarket(AgentMarket):
|
|
97
97
|
close_time: datetime
|
98
98
|
fee: float # proportion, from 0 to 1
|
99
99
|
|
100
|
-
INVALID_MARKET_ANSWER: HexStr = HexStr(
|
101
|
-
"0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF"
|
102
|
-
)
|
103
|
-
|
104
100
|
_binary_market_p_yes_history: list[Probability] | None = None
|
105
101
|
description: str | None = (
|
106
102
|
None # Omen markets don't have a description, so just default to None.
|
@@ -577,6 +573,66 @@ class OmenAgentMarket(AgentMarket):
|
|
577
573
|
def get_user_url(cls, keys: APIKeys) -> str:
|
578
574
|
return get_omen_user_url(keys.bet_from_address)
|
579
575
|
|
576
|
+
def get_buy_token_amount(
|
577
|
+
self, bet_amount: BetAmount, direction: bool
|
578
|
+
) -> TokenAmount:
|
579
|
+
"""
|
580
|
+
Note: this is only valid if the market instance's token pool is
|
581
|
+
up-to-date with the smart contract.
|
582
|
+
"""
|
583
|
+
outcome_token_pool = check_not_none(self.outcome_token_pool)
|
584
|
+
amount = get_buy_outcome_token_amount(
|
585
|
+
investment_amount=bet_amount.amount,
|
586
|
+
buy_direction=direction,
|
587
|
+
yes_outcome_pool_size=outcome_token_pool[OMEN_TRUE_OUTCOME],
|
588
|
+
no_outcome_pool_size=outcome_token_pool[OMEN_FALSE_OUTCOME],
|
589
|
+
fee=self.fee,
|
590
|
+
)
|
591
|
+
return TokenAmount(amount=amount, currency=self.currency)
|
592
|
+
|
593
|
+
def _get_buy_token_amount_from_smart_contract(
|
594
|
+
self, bet_amount: BetAmount, direction: bool
|
595
|
+
) -> TokenAmount:
|
596
|
+
received_token_amount_wei = Wei(
|
597
|
+
self.get_contract().calcBuyAmount(
|
598
|
+
investment_amount=xdai_to_wei(xDai(bet_amount.amount)),
|
599
|
+
outcome_index=self.get_outcome_index(
|
600
|
+
self.get_outcome_str_from_bool(direction)
|
601
|
+
),
|
602
|
+
)
|
603
|
+
)
|
604
|
+
received_token_amount = float(wei_to_xdai(received_token_amount_wei))
|
605
|
+
return TokenAmount(amount=received_token_amount, currency=self.currency)
|
606
|
+
|
607
|
+
def get_new_p_yes(self, bet_amount: BetAmount, direction: bool) -> Probability:
|
608
|
+
"""
|
609
|
+
Calculate the new p_yes based on the bet amount and direction.
|
610
|
+
"""
|
611
|
+
if not self.has_token_pool():
|
612
|
+
raise ValueError("Outcome token pool is required to calculate new p_yes.")
|
613
|
+
|
614
|
+
outcome_token_pool = check_not_none(self.outcome_token_pool)
|
615
|
+
yes_outcome_pool_size = outcome_token_pool[self.get_outcome_str_from_bool(True)]
|
616
|
+
no_outcome_pool_size = outcome_token_pool[self.get_outcome_str_from_bool(False)]
|
617
|
+
|
618
|
+
new_yes_outcome_pool_size = yes_outcome_pool_size + (
|
619
|
+
bet_amount.amount * (1 - self.fee)
|
620
|
+
)
|
621
|
+
new_no_outcome_pool_size = no_outcome_pool_size + (
|
622
|
+
bet_amount.amount * (1 - self.fee)
|
623
|
+
)
|
624
|
+
|
625
|
+
received_token_amount = self.get_buy_token_amount(bet_amount, direction).amount
|
626
|
+
if direction:
|
627
|
+
new_yes_outcome_pool_size -= received_token_amount
|
628
|
+
else:
|
629
|
+
new_no_outcome_pool_size -= received_token_amount
|
630
|
+
|
631
|
+
new_p_yes = new_no_outcome_pool_size / (
|
632
|
+
new_yes_outcome_pool_size + new_no_outcome_pool_size
|
633
|
+
)
|
634
|
+
return Probability(new_p_yes)
|
635
|
+
|
580
636
|
|
581
637
|
def get_omen_user_url(address: ChecksumAddress) -> str:
|
582
638
|
return f"https://gnosisscan.io/address/{address}"
|
@@ -876,17 +932,20 @@ def omen_fund_market_tx(
|
|
876
932
|
market_contract = market.get_contract()
|
877
933
|
collateral_token_contract = market_contract.get_collateral_token_contract()
|
878
934
|
|
879
|
-
|
880
|
-
auto_deposit_collateral_token(collateral_token_contract, funds, api_keys, web3)
|
935
|
+
amount_to_fund = collateral_token_contract.get_in_shares(funds, web3)
|
881
936
|
|
882
937
|
collateral_token_contract.approve(
|
883
938
|
api_keys=api_keys,
|
884
939
|
for_address=market_contract.address,
|
885
|
-
amount_wei=
|
940
|
+
amount_wei=amount_to_fund,
|
886
941
|
web3=web3,
|
887
942
|
)
|
888
943
|
|
889
|
-
|
944
|
+
if auto_deposit:
|
945
|
+
# In auto-depositing, we need to deposit the original `funds`, e.g. we can deposit 2 xDai, but receive 1.8 sDai, so for the funding we will use `amount_to_fund`.
|
946
|
+
auto_deposit_collateral_token(collateral_token_contract, funds, api_keys, web3)
|
947
|
+
|
948
|
+
market_contract.addFunding(api_keys, amount_to_fund, web3=web3)
|
890
949
|
|
891
950
|
|
892
951
|
def build_parent_collection_id() -> HexStr:
|
@@ -1162,3 +1221,33 @@ def withdraw_wxdai_to_xdai_to_keep_balance(
|
|
1162
1221
|
logger.info(
|
1163
1222
|
f"Withdrew {need_to_withdraw} wxDai to keep the balance above the minimum required balance {min_required_balance}."
|
1164
1223
|
)
|
1224
|
+
|
1225
|
+
|
1226
|
+
def get_buy_outcome_token_amount(
|
1227
|
+
investment_amount: float,
|
1228
|
+
buy_direction: bool,
|
1229
|
+
yes_outcome_pool_size: float,
|
1230
|
+
no_outcome_pool_size: float,
|
1231
|
+
fee: float,
|
1232
|
+
) -> float:
|
1233
|
+
"""
|
1234
|
+
Calculates the amount of outcome tokens received for a given investment
|
1235
|
+
|
1236
|
+
Taken from https://github.com/gnosis/conditional-tokens-market-makers/blob/6814c0247c745680bb13298d4f0dd7f5b574d0db/contracts/FixedProductMarketMaker.sol#L264
|
1237
|
+
"""
|
1238
|
+
investment_amount_minus_fees = investment_amount * (1 - fee)
|
1239
|
+
buy_token_pool_balance = (
|
1240
|
+
yes_outcome_pool_size if buy_direction else no_outcome_pool_size
|
1241
|
+
)
|
1242
|
+
|
1243
|
+
pool_balance = no_outcome_pool_size if buy_direction else yes_outcome_pool_size
|
1244
|
+
denominator = pool_balance + investment_amount_minus_fees
|
1245
|
+
ending_outcome_balance = buy_token_pool_balance * pool_balance / denominator
|
1246
|
+
|
1247
|
+
if ending_outcome_balance <= 0:
|
1248
|
+
raise ValueError("must have non-zero balances")
|
1249
|
+
|
1250
|
+
result = (
|
1251
|
+
buy_token_pool_balance + investment_amount_minus_fees - ending_outcome_balance
|
1252
|
+
)
|
1253
|
+
return result
|
@@ -19,8 +19,7 @@ from prediction_market_agent_tooling.gtypes import (
|
|
19
19
|
from prediction_market_agent_tooling.loggers import logger
|
20
20
|
from prediction_market_agent_tooling.markets.agent_market import FilterBy, SortBy
|
21
21
|
from prediction_market_agent_tooling.markets.omen.data_models import (
|
22
|
-
|
23
|
-
OMEN_TRUE_OUTCOME,
|
22
|
+
OMEN_BINARY_MARKET_OUTCOMES,
|
24
23
|
OmenBet,
|
25
24
|
OmenMarket,
|
26
25
|
OmenPosition,
|
@@ -204,7 +203,7 @@ class OmenSubgraphHandler(metaclass=SingletonMeta):
|
|
204
203
|
self,
|
205
204
|
creator: t.Optional[HexAddress] = None,
|
206
205
|
creator_in: t.Optional[t.Sequence[HexAddress]] = None,
|
207
|
-
outcomes: list[str] =
|
206
|
+
outcomes: list[str] = OMEN_BINARY_MARKET_OUTCOMES,
|
208
207
|
created_after: t.Optional[datetime] = None,
|
209
208
|
opened_before: t.Optional[datetime] = None,
|
210
209
|
opened_after: t.Optional[datetime] = None,
|
@@ -217,6 +216,7 @@ class OmenSubgraphHandler(metaclass=SingletonMeta):
|
|
217
216
|
id_in: list[str] | None = None,
|
218
217
|
excluded_questions: set[str] | None = None,
|
219
218
|
collateral_token_address_in: tuple[ChecksumAddress, ...] | None = None,
|
219
|
+
category: str | None = None,
|
220
220
|
) -> dict[str, t.Any]:
|
221
221
|
where_stms: dict[str, t.Any] = {
|
222
222
|
"isPendingArbitration": False,
|
@@ -282,6 +282,9 @@ class OmenSubgraphHandler(metaclass=SingletonMeta):
|
|
282
282
|
finalized_after
|
283
283
|
)
|
284
284
|
|
285
|
+
if category:
|
286
|
+
where_stms["category"] = category
|
287
|
+
|
285
288
|
# `excluded_question_titles` can not be an empty list, otherwise the API bugs out and returns nothing.
|
286
289
|
excluded_question_titles = [""]
|
287
290
|
if excluded_questions:
|
@@ -324,8 +327,10 @@ class OmenSubgraphHandler(metaclass=SingletonMeta):
|
|
324
327
|
# Additional filters, these can not be modified by the enums above.
|
325
328
|
created_after: datetime | None = None,
|
326
329
|
excluded_questions: set[str] | None = None, # question titles
|
327
|
-
collateral_token_address_in:
|
328
|
-
|
330
|
+
collateral_token_address_in: (
|
331
|
+
tuple[ChecksumAddress, ...] | None
|
332
|
+
) = SAFE_COLLATERAL_TOKEN_MARKETS,
|
333
|
+
category: str | None = None,
|
329
334
|
) -> t.List[OmenMarket]:
|
330
335
|
"""
|
331
336
|
Simplified `get_omen_binary_markets` method, which allows to fetch markets based on the filter_by and sort_by values.
|
@@ -363,6 +368,7 @@ class OmenSubgraphHandler(metaclass=SingletonMeta):
|
|
363
368
|
created_after=created_after,
|
364
369
|
excluded_questions=excluded_questions,
|
365
370
|
collateral_token_address_in=collateral_token_address_in,
|
371
|
+
category=category,
|
366
372
|
)
|
367
373
|
|
368
374
|
def get_omen_binary_markets(
|
@@ -383,9 +389,11 @@ class OmenSubgraphHandler(metaclass=SingletonMeta):
|
|
383
389
|
excluded_questions: set[str] | None = None, # question titles
|
384
390
|
sort_by_field: FieldPath | None = None,
|
385
391
|
sort_direction: str | None = None,
|
386
|
-
outcomes: list[str] =
|
387
|
-
collateral_token_address_in:
|
388
|
-
|
392
|
+
outcomes: list[str] = OMEN_BINARY_MARKET_OUTCOMES,
|
393
|
+
collateral_token_address_in: (
|
394
|
+
tuple[ChecksumAddress, ...] | None
|
395
|
+
) = SAFE_COLLATERAL_TOKEN_MARKETS,
|
396
|
+
category: str | None = None,
|
389
397
|
) -> t.List[OmenMarket]:
|
390
398
|
"""
|
391
399
|
Complete method to fetch Omen binary markets with various filters, use `get_omen_binary_markets_simple` for simplified version that uses FilterBy and SortBy enums.
|
@@ -406,6 +414,7 @@ class OmenSubgraphHandler(metaclass=SingletonMeta):
|
|
406
414
|
excluded_questions=excluded_questions,
|
407
415
|
liquidity_bigger_than=liquidity_bigger_than,
|
408
416
|
collateral_token_address_in=collateral_token_address_in,
|
417
|
+
category=category,
|
409
418
|
)
|
410
419
|
|
411
420
|
# These values can not be set to `None`, but they can be omitted.
|
@@ -211,7 +211,11 @@ def monitor_agent(agent: DeployedAgent) -> None:
|
|
211
211
|
key=f"{agent.name}_x_axis_column",
|
212
212
|
)
|
213
213
|
|
214
|
-
bets_df =
|
214
|
+
bets_df = (
|
215
|
+
pd.DataFrame(bets_info)
|
216
|
+
.sort_values(by=x_axis_column, ascending=False)
|
217
|
+
.reset_index(drop=True)
|
218
|
+
)
|
215
219
|
bets_df["x-axis-day"] = bets_df[x_axis_column].dt.date
|
216
220
|
|
217
221
|
# Metrics
|
@@ -1,4 +1,4 @@
|
|
1
|
-
from
|
1
|
+
from prediction_market_agent_tooling.tools.betting_strategies.utils import SimpleBet
|
2
2
|
|
3
3
|
|
4
4
|
def check_is_valid_probability(probability: float) -> None:
|
@@ -6,17 +6,12 @@ def check_is_valid_probability(probability: float) -> None:
|
|
6
6
|
raise ValueError("Probability must be between 0 and 1")
|
7
7
|
|
8
8
|
|
9
|
-
|
10
|
-
direction: bool
|
11
|
-
size: float
|
12
|
-
|
13
|
-
|
14
|
-
def get_kelly_bet(
|
9
|
+
def get_kelly_bet_simplified(
|
15
10
|
max_bet: float,
|
16
11
|
market_p_yes: float,
|
17
12
|
estimated_p_yes: float,
|
18
13
|
confidence: float,
|
19
|
-
) ->
|
14
|
+
) -> SimpleBet:
|
20
15
|
"""
|
21
16
|
Calculate the optimal bet amount using the Kelly Criterion for a binary outcome market.
|
22
17
|
|
@@ -57,4 +52,91 @@ def get_kelly_bet(
|
|
57
52
|
# Ensure bet size is non-negative does not exceed the wallet balance
|
58
53
|
bet_size = min(kelly_fraction * max_bet, max_bet)
|
59
54
|
|
60
|
-
return
|
55
|
+
return SimpleBet(direction=bet_direction, size=bet_size)
|
56
|
+
|
57
|
+
|
58
|
+
def get_kelly_bet_full(
|
59
|
+
yes_outcome_pool_size: float,
|
60
|
+
no_outcome_pool_size: float,
|
61
|
+
estimated_p_yes: float,
|
62
|
+
confidence: float,
|
63
|
+
max_bet: float,
|
64
|
+
fee: float = 0.0, # proportion, 0 to 1
|
65
|
+
) -> SimpleBet:
|
66
|
+
"""
|
67
|
+
Calculate the optimal bet amount using the Kelly Criterion for a binary outcome market.
|
68
|
+
|
69
|
+
'Full' as in it accounts for how the bet changes the market odds.
|
70
|
+
|
71
|
+
Taken from https://github.com/valory-xyz/trader/blob/main/strategies/kelly_criterion/kelly_criterion.py
|
72
|
+
|
73
|
+
with derivation in PR description: https://github.com/valory-xyz/trader/pull/119
|
74
|
+
|
75
|
+
```
|
76
|
+
Licensed under the Apache License, Version 2.0 (the "License");
|
77
|
+
you may not use this file except in compliance with the License.
|
78
|
+
You may obtain a copy of the License at
|
79
|
+
|
80
|
+
http://www.apache.org/licenses/LICENSE-2.0
|
81
|
+
|
82
|
+
Unless required by applicable law or agreed to in writing, software
|
83
|
+
distributed under the License is distributed on an "AS IS" BASIS,
|
84
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
85
|
+
See the License for the specific language governing permissions and
|
86
|
+
limitations under the License.
|
87
|
+
```
|
88
|
+
"""
|
89
|
+
check_is_valid_probability(estimated_p_yes)
|
90
|
+
check_is_valid_probability(confidence)
|
91
|
+
check_is_valid_probability(fee)
|
92
|
+
|
93
|
+
if max_bet == 0:
|
94
|
+
return SimpleBet(direction=True, size=0)
|
95
|
+
|
96
|
+
x = yes_outcome_pool_size
|
97
|
+
y = no_outcome_pool_size
|
98
|
+
p = estimated_p_yes
|
99
|
+
c = confidence
|
100
|
+
b = max_bet
|
101
|
+
f = 1 - fee
|
102
|
+
|
103
|
+
if x == y:
|
104
|
+
# Add a delta to prevent division by zero
|
105
|
+
y += 1e-10
|
106
|
+
|
107
|
+
numerator = (
|
108
|
+
-4 * x**2 * y
|
109
|
+
+ b * y**2 * p * c * f
|
110
|
+
+ 2 * b * x * y * p * c * f
|
111
|
+
+ b * x**2 * p * c * f
|
112
|
+
- 2 * b * y**2 * f
|
113
|
+
- 2 * b * x * y * f
|
114
|
+
+ (
|
115
|
+
(
|
116
|
+
4 * x**2 * y
|
117
|
+
- b * y**2 * p * c * f
|
118
|
+
- 2 * b * x * y * p * c * f
|
119
|
+
- b * x**2 * p * c * f
|
120
|
+
+ 2 * b * y**2 * f
|
121
|
+
+ 2 * b * x * y * f
|
122
|
+
)
|
123
|
+
** 2
|
124
|
+
- (
|
125
|
+
4
|
126
|
+
* (x**2 * f - y**2 * f)
|
127
|
+
* (
|
128
|
+
-4 * b * x * y**2 * p * c
|
129
|
+
- 4 * b * x**2 * y * p * c
|
130
|
+
+ 4 * b * x * y**2
|
131
|
+
)
|
132
|
+
)
|
133
|
+
)
|
134
|
+
** (1 / 2)
|
135
|
+
)
|
136
|
+
denominator = 2 * (x**2 * f - y**2 * f)
|
137
|
+
kelly_bet_amount = numerator / denominator
|
138
|
+
|
139
|
+
# Clip the bet size to max_bet to account for rounding errors.
|
140
|
+
return SimpleBet(
|
141
|
+
direction=kelly_bet_amount > 0, size=min(max_bet, abs(kelly_bet_amount))
|
142
|
+
)
|
@@ -1,29 +1,22 @@
|
|
1
|
-
import typing as t
|
2
1
|
from functools import reduce
|
3
2
|
|
4
3
|
import numpy as np
|
5
4
|
|
6
|
-
from prediction_market_agent_tooling.gtypes import Probability,
|
7
|
-
from prediction_market_agent_tooling.loggers import logger
|
8
|
-
from prediction_market_agent_tooling.markets.omen.data_models import OmenMarket
|
5
|
+
from prediction_market_agent_tooling.gtypes import Probability, Wei, xDai
|
9
6
|
from prediction_market_agent_tooling.markets.omen.omen import OmenAgentMarket
|
7
|
+
from prediction_market_agent_tooling.tools.betting_strategies.utils import SimpleBet
|
10
8
|
from prediction_market_agent_tooling.tools.utils import check_not_none
|
11
|
-
from prediction_market_agent_tooling.tools.web3_utils import
|
12
|
-
ONE_XDAI,
|
13
|
-
wei_to_xdai,
|
14
|
-
xdai_to_wei,
|
15
|
-
)
|
16
|
-
|
17
|
-
OutcomeIndex = t.Literal[0, 1]
|
9
|
+
from prediction_market_agent_tooling.tools.web3_utils import wei_to_xdai, xdai_to_wei
|
18
10
|
|
19
11
|
|
20
12
|
def get_market_moving_bet(
|
21
|
-
|
22
|
-
|
13
|
+
yes_outcome_pool_size: float,
|
14
|
+
no_outcome_pool_size: float,
|
15
|
+
market_p_yes: float,
|
16
|
+
target_p_yes: float,
|
17
|
+
fee: float = 0.0, # proportion, 0 to 1
|
23
18
|
max_iters: int = 100,
|
24
|
-
|
25
|
-
verbose: bool = False,
|
26
|
-
) -> t.Tuple[xDai, OutcomeIndex]:
|
19
|
+
) -> SimpleBet:
|
27
20
|
"""
|
28
21
|
Implements a binary search to determine the bet that will move the market's
|
29
22
|
`p_yes` to that of the target.
|
@@ -43,74 +36,97 @@ def get_market_moving_bet(
|
|
43
36
|
new_product - fixed_product = dx * na_y
|
44
37
|
dx = (new_product - fixed_product) / na_y
|
45
38
|
"""
|
46
|
-
|
47
|
-
|
48
|
-
prices = check_not_none(
|
49
|
-
market.outcomeTokenProbabilities, "No probabilities, is marked closed?"
|
50
|
-
)
|
51
|
-
if len(amounts) != 2 or len(prices) != 2:
|
52
|
-
raise ValueError("Only binary markets are supported.")
|
53
|
-
|
54
|
-
fixed_product = reduce(lambda x, y: x * y, amounts, 1)
|
55
|
-
assert np.isclose(float(sum(prices)), 1)
|
39
|
+
fixed_product = yes_outcome_pool_size * no_outcome_pool_size
|
40
|
+
bet_direction: bool = target_p_yes > market_p_yes
|
56
41
|
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
min_bet_amount = 0
|
62
|
-
max_bet_amount = 100 * sum(amounts) # TODO set a better upper bound
|
42
|
+
min_bet_amount = 0.0
|
43
|
+
max_bet_amount = 100 * (
|
44
|
+
yes_outcome_pool_size + no_outcome_pool_size
|
45
|
+
) # TODO set a better upper bound
|
63
46
|
|
64
47
|
# Binary search for the optimal bet amount
|
65
48
|
for _ in range(max_iters):
|
66
|
-
bet_amount = (min_bet_amount + max_bet_amount)
|
67
|
-
|
68
|
-
bet_amount
|
69
|
-
* (
|
70
|
-
xdai_to_wei(ONE_XDAI)
|
71
|
-
- check_not_none(market.fee, "No fee for the market.")
|
72
|
-
)
|
73
|
-
/ xdai_to_wei(ONE_XDAI)
|
74
|
-
)
|
49
|
+
bet_amount = (min_bet_amount + max_bet_amount) / 2
|
50
|
+
amounts_diff = bet_amount * (1 - fee)
|
75
51
|
|
76
52
|
# Initial new amounts are old amounts + equal new amounts for each outcome
|
77
|
-
|
78
|
-
|
53
|
+
yes_outcome_new_pool_size = yes_outcome_pool_size + amounts_diff
|
54
|
+
no_outcome_new_pool_size = no_outcome_pool_size + amounts_diff
|
55
|
+
new_amounts = {
|
56
|
+
True: yes_outcome_new_pool_size,
|
57
|
+
False: no_outcome_new_pool_size,
|
58
|
+
}
|
79
59
|
|
80
60
|
# Now give away tokens at `bet_outcome_index` to restore invariant
|
81
|
-
new_product =
|
82
|
-
dx = (new_product - fixed_product) / new_amounts[
|
83
|
-
|
84
|
-
|
85
|
-
if check_vs_contract:
|
86
|
-
expected_trade = market_agent.get_contract().calcBuyAmount(
|
87
|
-
investment_amount=wei_type(bet_amount),
|
88
|
-
outcome_index=bet_outcome_index,
|
89
|
-
)
|
90
|
-
assert np.isclose(float(expected_trade), dx)
|
91
|
-
|
92
|
-
new_amounts[bet_outcome_index] -= dx
|
61
|
+
new_product = yes_outcome_new_pool_size * no_outcome_new_pool_size
|
62
|
+
dx = (new_product - fixed_product) / new_amounts[not bet_direction]
|
63
|
+
new_amounts[bet_direction] -= dx
|
64
|
+
|
93
65
|
# Check that the invariant is restored
|
94
66
|
assert np.isclose(
|
95
|
-
reduce(lambda x, y: x * y, new_amounts, 1.0),
|
67
|
+
reduce(lambda x, y: x * y, list(new_amounts.values()), 1.0),
|
68
|
+
float(fixed_product),
|
96
69
|
)
|
97
|
-
|
98
|
-
|
99
|
-
if
|
100
|
-
outcome = market_agent.get_outcome_str(bet_outcome_index)
|
101
|
-
logger.debug(
|
102
|
-
f"Target p_yes: {target_p_yes:.2f}, bet: {wei_to_xdai(bet_amount_wei):.2f}{market_agent.currency} for {outcome}, new p_yes: {new_p_yes:.2f}"
|
103
|
-
)
|
104
|
-
if abs(target_p_yes - new_p_yes) < 0.01:
|
70
|
+
|
71
|
+
new_p_yes = Probability(new_amounts[False] / sum(list(new_amounts.values())))
|
72
|
+
if abs(target_p_yes - new_p_yes) < 1e-6:
|
105
73
|
break
|
106
74
|
elif new_p_yes > target_p_yes:
|
107
|
-
if
|
75
|
+
if bet_direction:
|
108
76
|
max_bet_amount = bet_amount
|
109
77
|
else:
|
110
78
|
min_bet_amount = bet_amount
|
111
79
|
else:
|
112
|
-
if
|
80
|
+
if bet_direction:
|
113
81
|
min_bet_amount = bet_amount
|
114
82
|
else:
|
115
83
|
max_bet_amount = bet_amount
|
116
|
-
|
84
|
+
|
85
|
+
return SimpleBet(direction=bet_direction, size=bet_amount)
|
86
|
+
|
87
|
+
|
88
|
+
def _sanity_check_omen_market_moving_bet(
|
89
|
+
bet_to_check: SimpleBet, market: OmenAgentMarket, target_p_yes: float
|
90
|
+
) -> None:
|
91
|
+
"""
|
92
|
+
A util function for checking that a bet moves the market to the target_p_yes
|
93
|
+
by calling the market's calcBuyAmount method from the smart contract, and
|
94
|
+
using the adjusted outcome pool sizes to calculate the new p_yes.
|
95
|
+
"""
|
96
|
+
buy_amount_ = market.get_contract().calcBuyAmount(
|
97
|
+
investment_amount=xdai_to_wei(xDai(bet_to_check.size)),
|
98
|
+
outcome_index=market.get_outcome_index(
|
99
|
+
market.get_outcome_str_from_bool(bet_to_check.direction)
|
100
|
+
),
|
101
|
+
)
|
102
|
+
buy_amount = float(wei_to_xdai(Wei(buy_amount_)))
|
103
|
+
|
104
|
+
outcome_token_pool = check_not_none(market.outcome_token_pool)
|
105
|
+
yes_outcome_pool_size = outcome_token_pool[market.get_outcome_str_from_bool(True)]
|
106
|
+
no_outcome_pool_size = outcome_token_pool[market.get_outcome_str_from_bool(False)]
|
107
|
+
market_const = yes_outcome_pool_size * no_outcome_pool_size
|
108
|
+
|
109
|
+
# When you buy 'yes' tokens, you add your bet size to the both pools, then
|
110
|
+
# subtract `buy_amount` from the 'yes' pool. And vice versa for 'no' tokens.
|
111
|
+
new_yes_outcome_pool_size = (
|
112
|
+
yes_outcome_pool_size
|
113
|
+
+ (bet_to_check.size * (1 - market.fee))
|
114
|
+
- float(bet_to_check.direction) * buy_amount
|
115
|
+
)
|
116
|
+
new_no_outcome_pool_size = (
|
117
|
+
no_outcome_pool_size
|
118
|
+
+ (bet_to_check.size * (1 - market.fee))
|
119
|
+
- float(not bet_to_check.direction) * buy_amount
|
120
|
+
)
|
121
|
+
new_market_const = new_yes_outcome_pool_size * new_no_outcome_pool_size
|
122
|
+
# Check the invariant is restored
|
123
|
+
assert np.isclose(new_market_const, market_const)
|
124
|
+
|
125
|
+
# Now check that the market's new p_yes is equal to the target_p_yes
|
126
|
+
new_p_yes = new_no_outcome_pool_size / (
|
127
|
+
new_yes_outcome_pool_size + new_no_outcome_pool_size
|
128
|
+
)
|
129
|
+
if not np.isclose(new_p_yes, target_p_yes, atol=0.01):
|
130
|
+
raise ValueError(
|
131
|
+
f"Bet does not move market to target_p_yes {target_p_yes=}. Got {new_p_yes=}"
|
132
|
+
)
|
@@ -1,3 +1,4 @@
|
|
1
|
+
import tenacity
|
1
2
|
from loguru import logger
|
2
3
|
|
3
4
|
from prediction_market_agent_tooling.config import APIKeys
|
@@ -76,6 +77,7 @@ Finally, write your final decision, write `decision: ` followed by either "yes i
|
|
76
77
|
|
77
78
|
|
78
79
|
@persistent_inmemory_cache
|
80
|
+
@tenacity.retry(stop=tenacity.stop_after_attempt(3), wait=tenacity.wait_fixed(1))
|
79
81
|
@observe()
|
80
82
|
def is_predictable_binary(
|
81
83
|
question: str,
|
@@ -111,6 +113,7 @@ def is_predictable_binary(
|
|
111
113
|
|
112
114
|
|
113
115
|
@persistent_inmemory_cache
|
116
|
+
@tenacity.retry(stop=tenacity.stop_after_attempt(3), wait=tenacity.wait_fixed(1))
|
114
117
|
@observe()
|
115
118
|
def is_predictable_without_description(
|
116
119
|
question: str,
|
@@ -0,0 +1,159 @@
|
|
1
|
+
import typing as t
|
2
|
+
from datetime import datetime
|
3
|
+
|
4
|
+
import numpy as np
|
5
|
+
from langfuse import Langfuse
|
6
|
+
from langfuse.client import TraceWithDetails
|
7
|
+
from pydantic import BaseModel
|
8
|
+
|
9
|
+
from prediction_market_agent_tooling.markets.data_models import (
|
10
|
+
ProbabilisticAnswer,
|
11
|
+
ResolvedBet,
|
12
|
+
Trade,
|
13
|
+
TradeType,
|
14
|
+
)
|
15
|
+
from prediction_market_agent_tooling.markets.omen.omen import OmenAgentMarket
|
16
|
+
from prediction_market_agent_tooling.tools.utils import add_utc_timezone_validator
|
17
|
+
|
18
|
+
|
19
|
+
class ProcessMarketTrace(BaseModel):
|
20
|
+
timestamp: datetime
|
21
|
+
market: OmenAgentMarket
|
22
|
+
answer: ProbabilisticAnswer
|
23
|
+
trades: list[Trade]
|
24
|
+
|
25
|
+
@property
|
26
|
+
def buy_trade(self) -> Trade:
|
27
|
+
buy_trades = [t for t in self.trades if t.trade_type == TradeType.BUY]
|
28
|
+
if len(buy_trades) == 1:
|
29
|
+
return buy_trades[0]
|
30
|
+
raise ValueError("No buy trade found")
|
31
|
+
|
32
|
+
@staticmethod
|
33
|
+
def from_langfuse_trace(
|
34
|
+
trace: TraceWithDetails,
|
35
|
+
) -> t.Optional["ProcessMarketTrace"]:
|
36
|
+
market = trace_to_omen_agent_market(trace)
|
37
|
+
answer = trace_to_answer(trace)
|
38
|
+
trades = trace_to_trades(trace)
|
39
|
+
|
40
|
+
if not market or not answer or not trades:
|
41
|
+
return None
|
42
|
+
|
43
|
+
return ProcessMarketTrace(
|
44
|
+
market=market,
|
45
|
+
answer=answer,
|
46
|
+
trades=trades,
|
47
|
+
timestamp=trace.timestamp,
|
48
|
+
)
|
49
|
+
|
50
|
+
|
51
|
+
class ResolvedBetWithTrace(BaseModel):
|
52
|
+
bet: ResolvedBet
|
53
|
+
trace: ProcessMarketTrace
|
54
|
+
|
55
|
+
|
56
|
+
def get_traces_for_agent(
|
57
|
+
agent_name: str,
|
58
|
+
trace_name: str,
|
59
|
+
from_timestamp: datetime,
|
60
|
+
has_output: bool,
|
61
|
+
client: Langfuse,
|
62
|
+
) -> list[TraceWithDetails]:
|
63
|
+
"""
|
64
|
+
Fetch agent traces using pagination
|
65
|
+
"""
|
66
|
+
page = 1 # index starts from 1
|
67
|
+
all_agent_traces = []
|
68
|
+
while True:
|
69
|
+
traces = client.fetch_traces(
|
70
|
+
name=trace_name,
|
71
|
+
limit=100,
|
72
|
+
page=page,
|
73
|
+
from_timestamp=from_timestamp,
|
74
|
+
)
|
75
|
+
if not traces.data:
|
76
|
+
break
|
77
|
+
page += 1
|
78
|
+
|
79
|
+
agent_traces = [
|
80
|
+
t
|
81
|
+
for t in traces.data
|
82
|
+
if t.session_id is not None and agent_name in t.session_id
|
83
|
+
]
|
84
|
+
if has_output:
|
85
|
+
agent_traces = [t for t in agent_traces if t.output is not None]
|
86
|
+
all_agent_traces.extend(agent_traces)
|
87
|
+
return all_agent_traces
|
88
|
+
|
89
|
+
|
90
|
+
def trace_to_omen_agent_market(trace: TraceWithDetails) -> OmenAgentMarket | None:
|
91
|
+
if not trace.input:
|
92
|
+
return None
|
93
|
+
if not trace.input["args"]:
|
94
|
+
return None
|
95
|
+
assert len(trace.input["args"]) == 2 and trace.input["args"][0] == "omen"
|
96
|
+
try:
|
97
|
+
# If the market model is invalid (e.g. outdated), it will raise an exception
|
98
|
+
market = OmenAgentMarket.model_validate(trace.input["args"][1])
|
99
|
+
return market
|
100
|
+
except Exception:
|
101
|
+
return None
|
102
|
+
|
103
|
+
|
104
|
+
def trace_to_answer(trace: TraceWithDetails) -> ProbabilisticAnswer:
|
105
|
+
assert trace.output is not None, "Trace output is None"
|
106
|
+
assert trace.output["answer"] is not None, "Trace output result is None"
|
107
|
+
return ProbabilisticAnswer.model_validate(trace.output["answer"])
|
108
|
+
|
109
|
+
|
110
|
+
def trace_to_trades(trace: TraceWithDetails) -> list[Trade]:
|
111
|
+
assert trace.output is not None, "Trace output is None"
|
112
|
+
assert trace.output["trades"] is not None, "Trace output trades is None"
|
113
|
+
return [Trade.model_validate(t) for t in trace.output["trades"]]
|
114
|
+
|
115
|
+
|
116
|
+
def get_closest_datetime_from_list(
|
117
|
+
ref_datetime: datetime, datetimes: list[datetime]
|
118
|
+
) -> int:
|
119
|
+
"""Get the index of the closest datetime to the reference datetime"""
|
120
|
+
if len(datetimes) == 1:
|
121
|
+
return 0
|
122
|
+
|
123
|
+
closest_datetime = min(datetimes, key=lambda dt: abs(dt - ref_datetime))
|
124
|
+
return datetimes.index(closest_datetime)
|
125
|
+
|
126
|
+
|
127
|
+
def get_trace_for_bet(
|
128
|
+
bet: ResolvedBet, traces: list[ProcessMarketTrace]
|
129
|
+
) -> ProcessMarketTrace | None:
|
130
|
+
# Filter for traces with the same market id
|
131
|
+
traces = [t for t in traces if t.market.id == bet.market_id]
|
132
|
+
|
133
|
+
# Filter for traces with the same bet outcome and amount
|
134
|
+
traces_for_bet: list[ProcessMarketTrace] = []
|
135
|
+
for t in traces:
|
136
|
+
# Cannot use exact comparison due to gas fees
|
137
|
+
if t.buy_trade.outcome == bet.outcome and np.isclose(
|
138
|
+
t.buy_trade.amount.amount, bet.amount.amount
|
139
|
+
):
|
140
|
+
traces_for_bet.append(t)
|
141
|
+
|
142
|
+
if not traces_for_bet:
|
143
|
+
return None
|
144
|
+
elif len(traces_for_bet) == 1:
|
145
|
+
return traces_for_bet[0]
|
146
|
+
else:
|
147
|
+
# In-case there are multiple traces for the same market, get the closest
|
148
|
+
# trace to the bet
|
149
|
+
closest_trace_index = get_closest_datetime_from_list(
|
150
|
+
add_utc_timezone_validator(bet.created_time),
|
151
|
+
[t.timestamp for t in traces_for_bet],
|
152
|
+
)
|
153
|
+
# Sanity check - the trace should be after the bet
|
154
|
+
if traces_for_bet[closest_trace_index].timestamp < add_utc_timezone_validator(
|
155
|
+
bet.created_time
|
156
|
+
):
|
157
|
+
return None
|
158
|
+
|
159
|
+
return traces_for_bet[closest_trace_index]
|
@@ -15,14 +15,18 @@ prediction_market_agent_tooling/benchmark/agents.py,sha256=BwE3U11tQq0rfOJBn-Xn5
|
|
15
15
|
prediction_market_agent_tooling/benchmark/benchmark.py,sha256=xiHKzZx5GHSsDerFHMZ9j_LXAXnSaITSvv67iPe3MEU,21095
|
16
16
|
prediction_market_agent_tooling/benchmark/utils.py,sha256=D0MfUkVZllmvcU0VOurk9tcKT7JTtwwOp-63zuCBVuc,2880
|
17
17
|
prediction_market_agent_tooling/config.py,sha256=9h68Nb9O1YZabZqtOBrH1S-4U5aIdLKfVYLSKspfUeA,6008
|
18
|
-
prediction_market_agent_tooling/deploy/agent.py,sha256=
|
18
|
+
prediction_market_agent_tooling/deploy/agent.py,sha256=xT0L1BLU89t7lfBcbbqojcH_4H7ElnXKMQB2wjFxwos,19112
|
19
19
|
prediction_market_agent_tooling/deploy/agent_example.py,sha256=dIIdZashExWk9tOdyDjw87AuUcGyM7jYxNChYrVK2dM,1001
|
20
|
-
prediction_market_agent_tooling/deploy/betting_strategy.py,sha256=
|
20
|
+
prediction_market_agent_tooling/deploy/betting_strategy.py,sha256=edatJ7temvl9TBTy05JjtFCAlaoVXCfLOQlYWJ61wvw,7636
|
21
21
|
prediction_market_agent_tooling/deploy/constants.py,sha256=M5ty8URipYMGe_G-RzxRydK3AFL6CyvmqCraJUrLBnE,82
|
22
22
|
prediction_market_agent_tooling/deploy/gcp/deploy.py,sha256=CYUgnfy-9XVk04kkxA_5yp0GE9Mw5caYqlFUZQ2j3ks,3739
|
23
23
|
prediction_market_agent_tooling/deploy/gcp/kubernetes_models.py,sha256=qYIHRxQLac3yxtZ8ChikiPG9O1aUQucHW0muTSm1nto,2627
|
24
24
|
prediction_market_agent_tooling/deploy/gcp/utils.py,sha256=oyW0jgrUT2Tr49c7GlpcMsYNQjoCSOcWis3q-MmVAhU,6089
|
25
|
-
prediction_market_agent_tooling/gtypes.py,sha256=
|
25
|
+
prediction_market_agent_tooling/gtypes.py,sha256=O77co9-GWmHJo_NyBzRVkli5L1xqweI28JmsyaAHUHs,2474
|
26
|
+
prediction_market_agent_tooling/jobs/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
27
|
+
prediction_market_agent_tooling/jobs/jobs.py,sha256=I07yh0GJ-xhlvQaOUQB8xlSnihhcbU2c7DZ4ZND14c0,1246
|
28
|
+
prediction_market_agent_tooling/jobs/jobs_models.py,sha256=wlBhh-D8pUegBCTpzTfla6crGlNMSqqNX7yDqMcRLYI,1326
|
29
|
+
prediction_market_agent_tooling/jobs/omen/omen_jobs.py,sha256=EAzo63vhSt1P7ddoDubdmvgzFf-ieQRTDCRLzYkH3js,3922
|
26
30
|
prediction_market_agent_tooling/loggers.py,sha256=JiBTgvb34O9dKHYKZyQ0UzojPUy6KSFQSTfbBIXopSY,3721
|
27
31
|
prediction_market_agent_tooling/markets/agent_market.py,sha256=nVJO1MY7_l-YP1Q7-mGLtRgIHK8M-yt3Ht1yyQPwImM,10176
|
28
32
|
prediction_market_agent_tooling/markets/categorize.py,sha256=jsoHWvZk9pU6n17oWSCcCxNNYVwlb_NXsZxKRI7vmsk,1301
|
@@ -37,11 +41,11 @@ prediction_market_agent_tooling/markets/metaculus/api.py,sha256=gvPQVAM5NlCyWzEM
|
|
37
41
|
prediction_market_agent_tooling/markets/metaculus/data_models.py,sha256=6TBy17xntdLBR61QCE5wddwTa_k2D0D8ZgK6p7sGUuc,2448
|
38
42
|
prediction_market_agent_tooling/markets/metaculus/metaculus.py,sha256=1aaainHlMExDSp6nfKY31iATQsaJx1LdYp9p2PkQVAs,3453
|
39
43
|
prediction_market_agent_tooling/markets/omen/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
40
|
-
prediction_market_agent_tooling/markets/omen/data_models.py,sha256=
|
41
|
-
prediction_market_agent_tooling/markets/omen/omen.py,sha256=
|
44
|
+
prediction_market_agent_tooling/markets/omen/data_models.py,sha256=PQyoENpXkhu5TXZhbUdDvKLsb2sQt8rwryPTVQ9fIeM,17029
|
45
|
+
prediction_market_agent_tooling/markets/omen/omen.py,sha256=TGXthQITMoYvx1iK388nHbv8U0DQeX_jyHb-OEinvfg,46676
|
42
46
|
prediction_market_agent_tooling/markets/omen/omen_contracts.py,sha256=MfaWfDDfEzHYVAbeT3Dgtl8KG7XsqEpdY3m3-rsOPwo,23588
|
43
47
|
prediction_market_agent_tooling/markets/omen/omen_resolving.py,sha256=Awaw1r32IBAClCktrXbYJ24RNyLDWcLb8RqAx6FGSkI,9529
|
44
|
-
prediction_market_agent_tooling/markets/omen/omen_subgraph_handler.py,sha256=
|
48
|
+
prediction_market_agent_tooling/markets/omen/omen_subgraph_handler.py,sha256=W3JIq3NDGoJuHZlPtv2Gv_EmCtYVwsgZwClSY-RloZw,30219
|
45
49
|
prediction_market_agent_tooling/markets/polymarket/api.py,sha256=HXmA1akA0qDj0m3e-GEvWG8x75pm6BX4H7YJPQcST7I,4767
|
46
50
|
prediction_market_agent_tooling/markets/polymarket/data_models.py,sha256=9CJzakyEcsn6DQBK2nOXjOMzTZBLAmK_KqevXvW17DI,4292
|
47
51
|
prediction_market_agent_tooling/markets/polymarket/data_models_web.py,sha256=IPsFT3FX9Ge5l5zR1nBd2w-sd5ue7oR8PJSW710vFWY,12479
|
@@ -51,15 +55,16 @@ prediction_market_agent_tooling/monitor/markets/manifold.py,sha256=GdYpgRX1GahDi
|
|
51
55
|
prediction_market_agent_tooling/monitor/markets/metaculus.py,sha256=S8zeDVN2aA6yvQykQNPb8GUGohczfJuCYt9Ds9ESCzs,1473
|
52
56
|
prediction_market_agent_tooling/monitor/markets/omen.py,sha256=jOLPnIbDU9syjnYtHfOb2xa6-Ize3vbplgh-8WWkuT4,3323
|
53
57
|
prediction_market_agent_tooling/monitor/markets/polymarket.py,sha256=I9z9aO1wncyGI3a09ihrw17JkeBKjAuMmC0I9pl_9o4,1781
|
54
|
-
prediction_market_agent_tooling/monitor/monitor.py,sha256=
|
58
|
+
prediction_market_agent_tooling/monitor/monitor.py,sha256=tvYzHJg8Nau4vGWVTngjUAcM26LcH3Emss76351xl2o,14636
|
55
59
|
prediction_market_agent_tooling/monitor/monitor_app.py,sha256=1e4LuzhAVjb7cPS6rGPZuZHMwMiNOeRhSxG8AVG-e0o,4839
|
56
60
|
prediction_market_agent_tooling/monitor/monitor_settings.py,sha256=Xiozs3AsufuJ04JOe1vjUri-IAMWHjjmc2ugGGiHNH4,947
|
57
61
|
prediction_market_agent_tooling/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
58
62
|
prediction_market_agent_tooling/tools/balances.py,sha256=nR8_dSfbm3yTOOmMAwhGlurftEiNo1w1WIVzbskjdmM,837
|
59
|
-
prediction_market_agent_tooling/tools/betting_strategies/kelly_criterion.py,sha256=
|
60
|
-
prediction_market_agent_tooling/tools/betting_strategies/market_moving.py,sha256=
|
63
|
+
prediction_market_agent_tooling/tools/betting_strategies/kelly_criterion.py,sha256=a_0VmjfQIVU2rrQjAcnWRl_WSYTqeq-7Wj35zUgpokY,4464
|
64
|
+
prediction_market_agent_tooling/tools/betting_strategies/market_moving.py,sha256=pCS0ISf6SgdhNx_t-h8_aHsLffnQ7kaLXVPPM7rGKJY,5163
|
61
65
|
prediction_market_agent_tooling/tools/betting_strategies/minimum_bet_to_win.py,sha256=-FUSuQQgjcWSSnoFxnlAyTeilY6raJABJVM2QKkFqAY,438
|
62
66
|
prediction_market_agent_tooling/tools/betting_strategies/stretch_bet_between.py,sha256=THMXwFlskvzbjnX_OiYtDSzI8XVFyULWfP2525_9UGc,429
|
67
|
+
prediction_market_agent_tooling/tools/betting_strategies/utils.py,sha256=kpIb-ci67Vc1Yqqaa-_S4OUkbhWSIYog4_Iwp69HU_k,97
|
63
68
|
prediction_market_agent_tooling/tools/cache.py,sha256=tGHHd9HCiE_hCCtPtloHZQdDfBuiow9YsqJNYi2Tx_0,499
|
64
69
|
prediction_market_agent_tooling/tools/contract.py,sha256=0XcT94HSiFMjca3f0WtoxzCGeCcj3Gcw-o0NGi6Fb3g,19627
|
65
70
|
prediction_market_agent_tooling/tools/costs.py,sha256=EaAJ7v9laD4VEV3d8B44M4u3_oEO_H16jRVCdoZ93Uw,954
|
@@ -68,8 +73,9 @@ prediction_market_agent_tooling/tools/google.py,sha256=SfVDxb3oEOUK8mpd0l3mTX9yb
|
|
68
73
|
prediction_market_agent_tooling/tools/hexbytes_custom.py,sha256=Bp94qgPjvjWf1Vb4lNzGFDXRdThw1rJ91vL6r2PWq5E,2096
|
69
74
|
prediction_market_agent_tooling/tools/image_gen/image_gen.py,sha256=HzRwBx62hOXBOmrtpkXaP9Qq1Ku03uUGdREocyjLQ_k,1266
|
70
75
|
prediction_market_agent_tooling/tools/image_gen/market_thumbnail_gen.py,sha256=8A3U2uxsCsOfLjru-6R_PPIAuiKY4qFkWp_GSBPV6-s,1280
|
71
|
-
prediction_market_agent_tooling/tools/is_predictable.py,sha256=
|
76
|
+
prediction_market_agent_tooling/tools/is_predictable.py,sha256=QapzvJVgUZdhucgmxhzWAQ885BwSwvYUi0SG8mkLQMQ,6738
|
72
77
|
prediction_market_agent_tooling/tools/langfuse_.py,sha256=jI_4ROxqo41CCnWGS1vN_AeDVhRzLMaQLxH3kxDu3L8,1153
|
78
|
+
prediction_market_agent_tooling/tools/langfuse_client_utils.py,sha256=EljGXIal3xApSzi5oaNJl4pRm66nayBWEpXR68SuVy0,4951
|
73
79
|
prediction_market_agent_tooling/tools/parallelism.py,sha256=Rz8QdVUWX8KCbr8UZfaC_b1GBWIb3bXwITUumuvBJ60,1633
|
74
80
|
prediction_market_agent_tooling/tools/safe.py,sha256=h0xOO0eNtitClf0fPkn-0oTc6A_bflDTee98V_aiV-A,5195
|
75
81
|
prediction_market_agent_tooling/tools/singleton.py,sha256=CiIELUiI-OeS7U7eeHEt0rnVhtQGzwoUdAgn_7u_GBM,729
|
@@ -78,8 +84,8 @@ prediction_market_agent_tooling/tools/tavily_storage/tavily_models.py,sha256=Uq2
|
|
78
84
|
prediction_market_agent_tooling/tools/tavily_storage/tavily_storage.py,sha256=xrtQH9v5pXycBRyc5j45pWqkSffkoc9efNIU1_G633Q,3706
|
79
85
|
prediction_market_agent_tooling/tools/utils.py,sha256=JE9YWtPPhnTgLiOyGAZDNG5K8nCwUY9IZEuAlm9UcxA,6611
|
80
86
|
prediction_market_agent_tooling/tools/web3_utils.py,sha256=IZDxHhUJH5RsaRkK9DW6z1RYdk2cz5RqLMZG3T6Gv1U,11602
|
81
|
-
prediction_market_agent_tooling-0.48.
|
82
|
-
prediction_market_agent_tooling-0.48.
|
83
|
-
prediction_market_agent_tooling-0.48.
|
84
|
-
prediction_market_agent_tooling-0.48.
|
85
|
-
prediction_market_agent_tooling-0.48.
|
87
|
+
prediction_market_agent_tooling-0.48.15.dist-info/LICENSE,sha256=6or154nLLU6bELzjh0mCreFjt0m2v72zLi3yHE0QbeE,7650
|
88
|
+
prediction_market_agent_tooling-0.48.15.dist-info/METADATA,sha256=vp4AH8yoRuOMnzAizjbHTWMTVxKWnvRLNrJxLt-Utm0,7811
|
89
|
+
prediction_market_agent_tooling-0.48.15.dist-info/WHEEL,sha256=sP946D7jFCHeNz5Iq4fL4Lu-PrWrFsgfLXbbkciIZwg,88
|
90
|
+
prediction_market_agent_tooling-0.48.15.dist-info/entry_points.txt,sha256=m8PukHbeH5g0IAAmOf_1Ahm-sGAMdhSSRQmwtpmi2s8,81
|
91
|
+
prediction_market_agent_tooling-0.48.15.dist-info/RECORD,,
|
File without changes
|
File without changes
|