prediction-market-agent-tooling 0.65.5__py3-none-any.whl → 0.69.17.dev1149__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- prediction_market_agent_tooling/abis/agentresultmapping.abi.json +192 -0
- prediction_market_agent_tooling/abis/erc1155.abi.json +352 -0
- prediction_market_agent_tooling/abis/processor.abi.json +16 -0
- prediction_market_agent_tooling/abis/swapr_quoter.abi.json +221 -0
- prediction_market_agent_tooling/abis/swapr_router.abi.json +634 -0
- prediction_market_agent_tooling/benchmark/benchmark.py +1 -1
- prediction_market_agent_tooling/benchmark/utils.py +13 -0
- prediction_market_agent_tooling/chains.py +1 -0
- prediction_market_agent_tooling/config.py +61 -2
- prediction_market_agent_tooling/data_download/langfuse_data_downloader.py +405 -0
- prediction_market_agent_tooling/deploy/agent.py +199 -67
- prediction_market_agent_tooling/deploy/agent_example.py +1 -1
- prediction_market_agent_tooling/deploy/betting_strategy.py +412 -68
- prediction_market_agent_tooling/deploy/constants.py +6 -0
- prediction_market_agent_tooling/gtypes.py +11 -1
- prediction_market_agent_tooling/jobs/jobs_models.py +2 -2
- prediction_market_agent_tooling/jobs/omen/omen_jobs.py +19 -20
- prediction_market_agent_tooling/loggers.py +9 -1
- prediction_market_agent_tooling/logprobs_parser.py +2 -1
- prediction_market_agent_tooling/markets/agent_market.py +106 -18
- prediction_market_agent_tooling/markets/blockchain_utils.py +37 -19
- prediction_market_agent_tooling/markets/data_models.py +120 -7
- prediction_market_agent_tooling/markets/manifold/data_models.py +5 -3
- prediction_market_agent_tooling/markets/manifold/manifold.py +21 -2
- prediction_market_agent_tooling/markets/manifold/utils.py +8 -2
- prediction_market_agent_tooling/markets/market_type.py +74 -0
- prediction_market_agent_tooling/markets/markets.py +7 -99
- prediction_market_agent_tooling/markets/metaculus/data_models.py +3 -3
- prediction_market_agent_tooling/markets/metaculus/metaculus.py +5 -8
- prediction_market_agent_tooling/markets/omen/cow_contracts.py +5 -1
- prediction_market_agent_tooling/markets/omen/data_models.py +63 -32
- prediction_market_agent_tooling/markets/omen/omen.py +112 -23
- prediction_market_agent_tooling/markets/omen/omen_constants.py +8 -0
- prediction_market_agent_tooling/markets/omen/omen_contracts.py +18 -203
- prediction_market_agent_tooling/markets/omen/omen_resolving.py +33 -13
- prediction_market_agent_tooling/markets/omen/omen_subgraph_handler.py +23 -18
- prediction_market_agent_tooling/markets/polymarket/api.py +123 -100
- prediction_market_agent_tooling/markets/polymarket/clob_manager.py +156 -0
- prediction_market_agent_tooling/markets/polymarket/constants.py +15 -0
- prediction_market_agent_tooling/markets/polymarket/data_models.py +95 -19
- prediction_market_agent_tooling/markets/polymarket/polymarket.py +373 -29
- prediction_market_agent_tooling/markets/polymarket/polymarket_contracts.py +35 -0
- prediction_market_agent_tooling/markets/polymarket/polymarket_subgraph_handler.py +91 -0
- prediction_market_agent_tooling/markets/polymarket/utils.py +1 -22
- prediction_market_agent_tooling/markets/seer/data_models.py +111 -17
- prediction_market_agent_tooling/markets/seer/exceptions.py +2 -0
- prediction_market_agent_tooling/markets/seer/price_manager.py +165 -50
- prediction_market_agent_tooling/markets/seer/seer.py +393 -106
- prediction_market_agent_tooling/markets/seer/seer_api.py +28 -0
- prediction_market_agent_tooling/markets/seer/seer_contracts.py +115 -5
- prediction_market_agent_tooling/markets/seer/seer_subgraph_handler.py +297 -66
- prediction_market_agent_tooling/markets/seer/subgraph_data_models.py +43 -8
- prediction_market_agent_tooling/markets/seer/swap_pool_handler.py +80 -0
- prediction_market_agent_tooling/tools/_generic_value.py +8 -2
- prediction_market_agent_tooling/tools/betting_strategies/kelly_criterion.py +271 -8
- prediction_market_agent_tooling/tools/betting_strategies/utils.py +6 -1
- prediction_market_agent_tooling/tools/caches/db_cache.py +219 -117
- prediction_market_agent_tooling/tools/caches/serializers.py +11 -2
- prediction_market_agent_tooling/tools/contract.py +480 -38
- prediction_market_agent_tooling/tools/contract_utils.py +61 -0
- prediction_market_agent_tooling/tools/cow/cow_order.py +218 -45
- prediction_market_agent_tooling/tools/cow/models.py +122 -0
- prediction_market_agent_tooling/tools/cow/semaphore.py +104 -0
- prediction_market_agent_tooling/tools/datetime_utc.py +14 -2
- prediction_market_agent_tooling/tools/db/db_manager.py +59 -0
- prediction_market_agent_tooling/tools/hexbytes_custom.py +4 -1
- prediction_market_agent_tooling/tools/httpx_cached_client.py +15 -6
- prediction_market_agent_tooling/tools/langfuse_client_utils.py +21 -8
- prediction_market_agent_tooling/tools/openai_utils.py +31 -0
- prediction_market_agent_tooling/tools/perplexity/perplexity_client.py +86 -0
- prediction_market_agent_tooling/tools/perplexity/perplexity_models.py +26 -0
- prediction_market_agent_tooling/tools/perplexity/perplexity_search.py +73 -0
- prediction_market_agent_tooling/tools/rephrase.py +71 -0
- prediction_market_agent_tooling/tools/singleton.py +11 -6
- prediction_market_agent_tooling/tools/streamlit_utils.py +188 -0
- prediction_market_agent_tooling/tools/tokens/auto_deposit.py +64 -0
- prediction_market_agent_tooling/tools/tokens/auto_withdraw.py +8 -0
- prediction_market_agent_tooling/tools/tokens/slippage.py +21 -0
- prediction_market_agent_tooling/tools/tokens/usd.py +5 -2
- prediction_market_agent_tooling/tools/utils.py +61 -3
- prediction_market_agent_tooling/tools/web3_utils.py +63 -9
- {prediction_market_agent_tooling-0.65.5.dist-info → prediction_market_agent_tooling-0.69.17.dev1149.dist-info}/METADATA +13 -9
- {prediction_market_agent_tooling-0.65.5.dist-info → prediction_market_agent_tooling-0.69.17.dev1149.dist-info}/RECORD +86 -64
- {prediction_market_agent_tooling-0.65.5.dist-info → prediction_market_agent_tooling-0.69.17.dev1149.dist-info}/WHEEL +1 -1
- prediction_market_agent_tooling/abis/omen_agentresultmapping.abi.json +0 -171
- prediction_market_agent_tooling/markets/polymarket/data_models_web.py +0 -420
- {prediction_market_agent_tooling-0.65.5.dist-info → prediction_market_agent_tooling-0.69.17.dev1149.dist-info}/entry_points.txt +0 -0
- {prediction_market_agent_tooling-0.65.5.dist-info → prediction_market_agent_tooling-0.69.17.dev1149.dist-info/licenses}/LICENSE +0 -0
|
@@ -5,3 +5,9 @@ INVALID_OUTCOME_LOWERCASE_IDENTIFIER = "invalid"
|
|
|
5
5
|
# Market-agnostic outcome identifiers
|
|
6
6
|
YES_OUTCOME_LOWERCASE_IDENTIFIER = "yes"
|
|
7
7
|
NO_OUTCOME_LOWERCASE_IDENTIFIER = "no"
|
|
8
|
+
UP_OUTCOME_LOWERCASE_IDENTIFIER = "up"
|
|
9
|
+
DOWN_OUTCOME_LOWERCASE_IDENTIFIER = "down"
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def is_invalid_outcome(outcome: str) -> bool:
|
|
13
|
+
return INVALID_OUTCOME_LOWERCASE_IDENTIFIER in outcome.lower()
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import typing as t
|
|
2
2
|
from decimal import Decimal
|
|
3
|
-
from typing import NewType
|
|
3
|
+
from typing import Annotated, NewType, TypeAlias
|
|
4
4
|
|
|
5
5
|
from eth_typing.evm import ( # noqa: F401 # Import for the sake of easy importing with others from here.
|
|
6
6
|
Address,
|
|
@@ -8,6 +8,7 @@ from eth_typing.evm import ( # noqa: F401 # Import for the sake of easy import
|
|
|
8
8
|
HexAddress,
|
|
9
9
|
HexStr,
|
|
10
10
|
)
|
|
11
|
+
from pydantic import BeforeValidator
|
|
11
12
|
from pydantic.types import SecretStr
|
|
12
13
|
from pydantic.v1.types import SecretStr as SecretStrV1
|
|
13
14
|
from web3 import Web3
|
|
@@ -18,6 +19,7 @@ from web3.types import ( # noqa: F401 # Import for the sake of easy importing
|
|
|
18
19
|
)
|
|
19
20
|
from web3.types import Wei as Web3Wei
|
|
20
21
|
|
|
22
|
+
from prediction_market_agent_tooling.gtypes import ChecksumAddress
|
|
21
23
|
from prediction_market_agent_tooling.tools._generic_value import _GenericValue
|
|
22
24
|
from prediction_market_agent_tooling.tools.datetime_utc import ( # noqa: F401 # Import for the sake of easy importing with others from here.
|
|
23
25
|
DatetimeUTC,
|
|
@@ -26,6 +28,14 @@ from prediction_market_agent_tooling.tools.hexbytes_custom import ( # noqa: F40
|
|
|
26
28
|
HexBytes,
|
|
27
29
|
)
|
|
28
30
|
|
|
31
|
+
VerifiedChecksumAddress: TypeAlias = Annotated[
|
|
32
|
+
ChecksumAddress, BeforeValidator(Web3.to_checksum_address)
|
|
33
|
+
]
|
|
34
|
+
VerifiedChecksumAddressOrNone: TypeAlias = Annotated[
|
|
35
|
+
ChecksumAddress | None,
|
|
36
|
+
BeforeValidator(lambda x: Web3.to_checksum_address(x) if x else None),
|
|
37
|
+
]
|
|
38
|
+
|
|
29
39
|
|
|
30
40
|
class CollateralToken(_GenericValue[int | float | str | Decimal, float], parser=float):
|
|
31
41
|
"""
|
|
@@ -9,7 +9,7 @@ from prediction_market_agent_tooling.deploy.betting_strategy import (
|
|
|
9
9
|
from prediction_market_agent_tooling.gtypes import USD, Probability
|
|
10
10
|
from prediction_market_agent_tooling.markets.agent_market import (
|
|
11
11
|
AgentMarket,
|
|
12
|
-
|
|
12
|
+
ProcessedMarket,
|
|
13
13
|
)
|
|
14
14
|
from prediction_market_agent_tooling.markets.omen.data_models import (
|
|
15
15
|
OMEN_FALSE_OUTCOME,
|
|
@@ -64,7 +64,7 @@ class JobAgentMarket(AgentMarket, ABC):
|
|
|
64
64
|
@abstractmethod
|
|
65
65
|
def submit_job_result(
|
|
66
66
|
self, agent_name: str, max_bond: USD, result: str
|
|
67
|
-
) ->
|
|
67
|
+
) -> ProcessedMarket:
|
|
68
68
|
"""Submit the completed result for this job."""
|
|
69
69
|
|
|
70
70
|
def to_simple_job(self, max_bond: USD) -> SimpleJob:
|
|
@@ -1,13 +1,9 @@
|
|
|
1
1
|
import typing as t
|
|
2
2
|
|
|
3
3
|
from prediction_market_agent_tooling.config import APIKeys
|
|
4
|
-
from prediction_market_agent_tooling.deploy.betting_strategy import (
|
|
5
|
-
KellyBettingStrategy,
|
|
6
|
-
TradeType,
|
|
7
|
-
)
|
|
8
4
|
from prediction_market_agent_tooling.gtypes import USD
|
|
9
5
|
from prediction_market_agent_tooling.jobs.jobs_models import JobAgentMarket
|
|
10
|
-
from prediction_market_agent_tooling.markets.agent_market import
|
|
6
|
+
from prediction_market_agent_tooling.markets.agent_market import ProcessedMarket
|
|
11
7
|
from prediction_market_agent_tooling.markets.data_models import PlacedTrade, Trade
|
|
12
8
|
from prediction_market_agent_tooling.markets.omen.omen import (
|
|
13
9
|
OmenAgentMarket,
|
|
@@ -72,7 +68,7 @@ class OmenJobAgentMarket(OmenAgentMarket, JobAgentMarket):
|
|
|
72
68
|
|
|
73
69
|
def submit_job_result(
|
|
74
70
|
self, agent_name: str, max_bond: USD, result: str
|
|
75
|
-
) ->
|
|
71
|
+
) -> ProcessedMarket:
|
|
76
72
|
if not APIKeys().enable_ipfs_upload:
|
|
77
73
|
raise RuntimeError(
|
|
78
74
|
f"ENABLE_IPFS_UPLOAD must be set to True to upload job results."
|
|
@@ -81,7 +77,7 @@ class OmenJobAgentMarket(OmenAgentMarket, JobAgentMarket):
|
|
|
81
77
|
trade = self.get_job_trade(max_bond, result)
|
|
82
78
|
buy_id = self.buy_tokens(outcome=trade.outcome, amount=trade.amount)
|
|
83
79
|
|
|
84
|
-
processed_traded_market =
|
|
80
|
+
processed_traded_market = ProcessedMarket(
|
|
85
81
|
answer=self.get_job_answer(result),
|
|
86
82
|
trades=[PlacedTrade.from_trade(trade, id=buy_id)],
|
|
87
83
|
)
|
|
@@ -92,20 +88,21 @@ class OmenJobAgentMarket(OmenAgentMarket, JobAgentMarket):
|
|
|
92
88
|
return processed_traded_market
|
|
93
89
|
|
|
94
90
|
def get_job_trade(self, max_bond: USD, result: str) -> Trade:
|
|
91
|
+
raise NotImplementedError("TODO: Refactor to avoid circular imports.")
|
|
95
92
|
# 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.
|
|
96
|
-
strategy =
|
|
97
|
-
required_trades = strategy.calculate_trades(
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
)
|
|
102
|
-
assert (
|
|
103
|
-
|
|
104
|
-
), f"Shouldn't process same job twice: {required_trades}"
|
|
105
|
-
trade = required_trades[0]
|
|
106
|
-
assert trade.trade_type == TradeType.BUY, "Should only buy on job markets."
|
|
107
|
-
assert trade.outcome, "Should buy only YES on job markets."
|
|
108
|
-
return required_trades[0]
|
|
93
|
+
# strategy = FullBinaryKellyBettingStrategy(max_position_amount=max_bond)
|
|
94
|
+
# required_trades = strategy.calculate_trades(
|
|
95
|
+
# existing_position=None,
|
|
96
|
+
# answer=self.get_job_answer(result),
|
|
97
|
+
# market=self,
|
|
98
|
+
# )
|
|
99
|
+
# assert (
|
|
100
|
+
# len(required_trades) == 1
|
|
101
|
+
# ), f"Shouldn't process same job twice: {required_trades}"
|
|
102
|
+
# trade = required_trades[0]
|
|
103
|
+
# assert trade.trade_type == TradeType.BUY, "Should only buy on job markets."
|
|
104
|
+
# assert trade.outcome, "Should buy only YES on job markets."
|
|
105
|
+
# return required_trades[0]
|
|
109
106
|
|
|
110
107
|
@staticmethod
|
|
111
108
|
def from_omen_market(market: OmenMarket) -> "OmenJobAgentMarket":
|
|
@@ -133,4 +130,6 @@ class OmenJobAgentMarket(OmenAgentMarket, JobAgentMarket):
|
|
|
133
130
|
condition=market.condition,
|
|
134
131
|
finalized_time=market.finalized_time,
|
|
135
132
|
fees=market.fees,
|
|
133
|
+
upper_bound=market.upper_bound,
|
|
134
|
+
lower_bound=market.lower_bound,
|
|
136
135
|
)
|
|
@@ -10,6 +10,8 @@ from pydantic_settings import BaseSettings, SettingsConfigDict
|
|
|
10
10
|
from pythonjsonlogger import jsonlogger
|
|
11
11
|
from tenacity import RetryError
|
|
12
12
|
|
|
13
|
+
UNPATCHED_PRINT_FN = builtins.print
|
|
14
|
+
|
|
13
15
|
|
|
14
16
|
class LogFormat(str, Enum):
|
|
15
17
|
DEFAULT = "default"
|
|
@@ -153,7 +155,13 @@ def print_using_logger_info(
|
|
|
153
155
|
end: str = "\n",
|
|
154
156
|
**kwargs: t.Any,
|
|
155
157
|
) -> None:
|
|
156
|
-
logger.
|
|
158
|
+
# If `logger.exception` is used, loguru+traceback somehow uses `print` statement to format the error stack,
|
|
159
|
+
# if that happens, without this if condition, it errors out because of deadlock and/or recursion errors.
|
|
160
|
+
# This is hacky, but that's exactly how loguru is checking for it internally..
|
|
161
|
+
if any(getattr(handler._lock_acquired, "acquired", False) for handler in logger._core.handlers.values()): # type: ignore # They use stubs and didn't type this.
|
|
162
|
+
UNPATCHED_PRINT_FN(*values, sep=sep, end=end, **kwargs)
|
|
163
|
+
else:
|
|
164
|
+
logger.info(sep.join(map(str, values)) + end)
|
|
157
165
|
|
|
158
166
|
|
|
159
167
|
patch_logger()
|
|
@@ -61,7 +61,8 @@ class LogprobsParser:
|
|
|
61
61
|
(
|
|
62
62
|
i
|
|
63
63
|
for i in range(result_start_index, len(logprobs))
|
|
64
|
-
if logprobs[i]["token"]
|
|
64
|
+
if logprobs[i]["token"]
|
|
65
|
+
in {",", '"', ",\n", "\",\n'", '",\n', '"\n', "\n"}
|
|
65
66
|
),
|
|
66
67
|
len(logprobs) - 1,
|
|
67
68
|
)
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import typing as t
|
|
2
|
+
from datetime import timedelta
|
|
2
3
|
from enum import Enum
|
|
3
4
|
from math import prod
|
|
4
5
|
|
|
@@ -10,17 +11,20 @@ from web3 import Web3
|
|
|
10
11
|
from prediction_market_agent_tooling.benchmark.utils import get_most_probable_outcome
|
|
11
12
|
from prediction_market_agent_tooling.config import APIKeys
|
|
12
13
|
from prediction_market_agent_tooling.deploy.constants import (
|
|
13
|
-
|
|
14
|
+
DOWN_OUTCOME_LOWERCASE_IDENTIFIER,
|
|
14
15
|
NO_OUTCOME_LOWERCASE_IDENTIFIER,
|
|
16
|
+
UP_OUTCOME_LOWERCASE_IDENTIFIER,
|
|
15
17
|
YES_OUTCOME_LOWERCASE_IDENTIFIER,
|
|
18
|
+
is_invalid_outcome,
|
|
16
19
|
)
|
|
17
20
|
from prediction_market_agent_tooling.gtypes import (
|
|
18
21
|
OutcomeStr,
|
|
19
22
|
OutcomeToken,
|
|
20
23
|
OutcomeWei,
|
|
21
24
|
Probability,
|
|
25
|
+
Wei,
|
|
26
|
+
xDai,
|
|
22
27
|
)
|
|
23
|
-
from prediction_market_agent_tooling.loggers import logger
|
|
24
28
|
from prediction_market_agent_tooling.markets.data_models import (
|
|
25
29
|
USD,
|
|
26
30
|
Bet,
|
|
@@ -41,9 +45,6 @@ from prediction_market_agent_tooling.tools.utils import (
|
|
|
41
45
|
|
|
42
46
|
class ProcessedMarket(BaseModel):
|
|
43
47
|
answer: CategoricalProbabilisticAnswer
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
class ProcessedTradedMarket(ProcessedMarket):
|
|
47
48
|
trades: list[PlacedTrade]
|
|
48
49
|
|
|
49
50
|
|
|
@@ -61,6 +62,24 @@ class FilterBy(str, Enum):
|
|
|
61
62
|
NONE = "none"
|
|
62
63
|
|
|
63
64
|
|
|
65
|
+
class ParentMarket(BaseModel):
|
|
66
|
+
market: "AgentMarket"
|
|
67
|
+
parent_outcome: int
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
class QuestionType(str, Enum):
|
|
71
|
+
ALL = "all"
|
|
72
|
+
CATEGORICAL = "categorical"
|
|
73
|
+
SCALAR = "scalar"
|
|
74
|
+
BINARY = "binary"
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
class ConditionalFilterType(Enum):
|
|
78
|
+
ALL = 1
|
|
79
|
+
ONLY_CONDITIONAL = 2
|
|
80
|
+
ONLY_NOT_CONDITIONAL = 3
|
|
81
|
+
|
|
82
|
+
|
|
64
83
|
class AgentMarket(BaseModel):
|
|
65
84
|
"""
|
|
66
85
|
Common market class that can be created from vendor specific markets.
|
|
@@ -83,6 +102,11 @@ class AgentMarket(BaseModel):
|
|
|
83
102
|
volume: CollateralToken | None
|
|
84
103
|
fees: MarketFees
|
|
85
104
|
|
|
105
|
+
upper_bound: Wei | None = None
|
|
106
|
+
lower_bound: Wei | None = None
|
|
107
|
+
|
|
108
|
+
parent: ParentMarket | None = None
|
|
109
|
+
|
|
86
110
|
@field_validator("probabilities")
|
|
87
111
|
def validate_probabilities(
|
|
88
112
|
cls,
|
|
@@ -92,11 +116,6 @@ class AgentMarket(BaseModel):
|
|
|
92
116
|
outcomes: t.Sequence[OutcomeStr] = check_not_none(info.data.get("outcomes"))
|
|
93
117
|
if set(probs.keys()) != set(outcomes):
|
|
94
118
|
raise ValueError("Keys of `probabilities` must match `outcomes` exactly.")
|
|
95
|
-
total = float(sum(probs.values()))
|
|
96
|
-
if not 0.999 <= total <= 1.001:
|
|
97
|
-
# We simply log a warning because for some use-cases (e.g. existing positions), the
|
|
98
|
-
# markets might be already closed hence no reliable outcome token prices exist anymore.
|
|
99
|
-
logger.warning(f"Probabilities for market {info.data=} do not sum to 1.")
|
|
100
119
|
return probs
|
|
101
120
|
|
|
102
121
|
@field_validator("outcome_token_pool")
|
|
@@ -115,6 +134,9 @@ class AgentMarket(BaseModel):
|
|
|
115
134
|
)
|
|
116
135
|
return outcome_token_pool
|
|
117
136
|
|
|
137
|
+
def have_bet_on_market_since(self, keys: APIKeys, since: timedelta) -> bool:
|
|
138
|
+
raise NotImplementedError("Subclasses must implement this method")
|
|
139
|
+
|
|
118
140
|
def get_outcome_token_pool_by_outcome(self, outcome: OutcomeStr) -> OutcomeToken:
|
|
119
141
|
if self.outcome_token_pool is None or not self.outcome_token_pool:
|
|
120
142
|
return OutcomeToken(0)
|
|
@@ -129,6 +151,23 @@ class AgentMarket(BaseModel):
|
|
|
129
151
|
if "fees" not in data and "fee" in data:
|
|
130
152
|
data["fees"] = MarketFees(absolute=0.0, bet_proportion=data["fee"])
|
|
131
153
|
del data["fee"]
|
|
154
|
+
# Backward compatibility for older `AgentMarket` without `probabilities`.
|
|
155
|
+
if "probabilities" not in data and "current_p_yes" in data:
|
|
156
|
+
yes_outcome = data["outcomes"][
|
|
157
|
+
[o.lower() for o in data["outcomes"]].index(
|
|
158
|
+
YES_OUTCOME_LOWERCASE_IDENTIFIER
|
|
159
|
+
)
|
|
160
|
+
]
|
|
161
|
+
no_outcome = data["outcomes"][
|
|
162
|
+
[o.lower() for o in data["outcomes"]].index(
|
|
163
|
+
NO_OUTCOME_LOWERCASE_IDENTIFIER
|
|
164
|
+
)
|
|
165
|
+
]
|
|
166
|
+
data["probabilities"] = {
|
|
167
|
+
yes_outcome: data["current_p_yes"],
|
|
168
|
+
no_outcome: 1 - data["current_p_yes"],
|
|
169
|
+
}
|
|
170
|
+
del data["current_p_yes"]
|
|
132
171
|
return data
|
|
133
172
|
|
|
134
173
|
def market_outcome_for_probability_key(
|
|
@@ -149,6 +188,17 @@ class AgentMarket(BaseModel):
|
|
|
149
188
|
f"Could not find probability for market outcome {market_outcome}"
|
|
150
189
|
)
|
|
151
190
|
|
|
191
|
+
@property
|
|
192
|
+
def question_type(self) -> QuestionType:
|
|
193
|
+
if self.is_binary:
|
|
194
|
+
return QuestionType.BINARY
|
|
195
|
+
|
|
196
|
+
elif self.is_scalar:
|
|
197
|
+
return QuestionType.SCALAR
|
|
198
|
+
|
|
199
|
+
else:
|
|
200
|
+
return QuestionType.CATEGORICAL
|
|
201
|
+
|
|
152
202
|
@property
|
|
153
203
|
def is_binary(self) -> bool:
|
|
154
204
|
# 3 outcomes can also be binary if 3rd outcome is invalid (Seer)
|
|
@@ -162,11 +212,39 @@ class AgentMarket(BaseModel):
|
|
|
162
212
|
|
|
163
213
|
if len(lowercase_outcomes) == 3:
|
|
164
214
|
invalid_outcome = lowercase_outcomes[-1]
|
|
165
|
-
has_invalid =
|
|
215
|
+
has_invalid = is_invalid_outcome(invalid_outcome)
|
|
166
216
|
return has_yes and has_no and has_invalid
|
|
167
217
|
|
|
168
218
|
return has_yes and has_no
|
|
169
219
|
|
|
220
|
+
@property
|
|
221
|
+
def is_scalar(self) -> bool:
|
|
222
|
+
# 3 outcomes can also be binary if 3rd outcome is invalid (Seer)
|
|
223
|
+
if len(self.outcomes) not in [2, 3]:
|
|
224
|
+
return False
|
|
225
|
+
|
|
226
|
+
lowercase_outcomes = [outcome.lower() for outcome in self.outcomes]
|
|
227
|
+
|
|
228
|
+
has_up = UP_OUTCOME_LOWERCASE_IDENTIFIER in lowercase_outcomes
|
|
229
|
+
has_down = DOWN_OUTCOME_LOWERCASE_IDENTIFIER in lowercase_outcomes
|
|
230
|
+
|
|
231
|
+
if len(lowercase_outcomes) == 3:
|
|
232
|
+
invalid_outcome = lowercase_outcomes[-1]
|
|
233
|
+
has_invalid = is_invalid_outcome(invalid_outcome)
|
|
234
|
+
return has_up and has_down and has_invalid
|
|
235
|
+
|
|
236
|
+
return has_up and has_down
|
|
237
|
+
|
|
238
|
+
@property
|
|
239
|
+
def p_up(self) -> Probability:
|
|
240
|
+
probs_lowercase = {o.lower(): p for o, p in self.probabilities.items()}
|
|
241
|
+
return check_not_none(probs_lowercase.get(UP_OUTCOME_LOWERCASE_IDENTIFIER))
|
|
242
|
+
|
|
243
|
+
@property
|
|
244
|
+
def p_down(self) -> Probability:
|
|
245
|
+
probs_lowercase = {o.lower(): p for o, p in self.probabilities.items()}
|
|
246
|
+
return check_not_none(probs_lowercase.get(DOWN_OUTCOME_LOWERCASE_IDENTIFIER))
|
|
247
|
+
|
|
170
248
|
@property
|
|
171
249
|
def p_yes(self) -> Probability:
|
|
172
250
|
probs_lowercase = {o.lower(): p for o, p in self.probabilities.items()}
|
|
@@ -337,7 +415,8 @@ class AgentMarket(BaseModel):
|
|
|
337
415
|
filter_by: FilterBy = FilterBy.OPEN,
|
|
338
416
|
created_after: t.Optional[DatetimeUTC] = None,
|
|
339
417
|
excluded_questions: set[str] | None = None,
|
|
340
|
-
|
|
418
|
+
question_type: QuestionType = QuestionType.ALL,
|
|
419
|
+
conditional_filter_type: ConditionalFilterType = ConditionalFilterType.ONLY_NOT_CONDITIONAL,
|
|
341
420
|
) -> t.Sequence["AgentMarket"]:
|
|
342
421
|
raise NotImplementedError("Subclasses must implement this method")
|
|
343
422
|
|
|
@@ -380,7 +459,7 @@ class AgentMarket(BaseModel):
|
|
|
380
459
|
|
|
381
460
|
def store_trades(
|
|
382
461
|
self,
|
|
383
|
-
traded_market:
|
|
462
|
+
traded_market: ProcessedMarket | None,
|
|
384
463
|
keys: APIKeys,
|
|
385
464
|
agent_name: str,
|
|
386
465
|
web3: Web3 | None = None,
|
|
@@ -439,11 +518,20 @@ class AgentMarket(BaseModel):
|
|
|
439
518
|
)
|
|
440
519
|
|
|
441
520
|
def get_outcome_index(self, outcome: OutcomeStr) -> int:
|
|
442
|
-
|
|
521
|
+
"""Get the index of the given outcome in the market's outcomes."""
|
|
443
522
|
try:
|
|
444
|
-
return
|
|
445
|
-
except ValueError:
|
|
446
|
-
raise ValueError(
|
|
523
|
+
return [o.lower() for o in self.outcomes].index(outcome.lower())
|
|
524
|
+
except ValueError as e:
|
|
525
|
+
raise ValueError(
|
|
526
|
+
f"Outcome '{outcome}' not found in market outcomes: {self.outcomes}"
|
|
527
|
+
) from e
|
|
528
|
+
|
|
529
|
+
def ensure_min_native_balance(
|
|
530
|
+
self,
|
|
531
|
+
min_required_balance: xDai,
|
|
532
|
+
multiplier: float = 3.0,
|
|
533
|
+
) -> None:
|
|
534
|
+
raise NotImplementedError("Subclass must implement this method")
|
|
447
535
|
|
|
448
536
|
def get_token_balance(self, user_id: str, outcome: OutcomeStr) -> OutcomeToken:
|
|
449
537
|
raise NotImplementedError("Subclasses must implement this method")
|
|
@@ -492,7 +580,7 @@ class AgentMarket(BaseModel):
|
|
|
492
580
|
|
|
493
581
|
@staticmethod
|
|
494
582
|
def get_user_id(api_keys: APIKeys) -> str:
|
|
495
|
-
|
|
583
|
+
return api_keys.bet_from_address
|
|
496
584
|
|
|
497
585
|
def get_most_recent_trade_datetime(self, user_id: str) -> DatetimeUTC | None:
|
|
498
586
|
raise NotImplementedError("Subclasses must implement this method")
|
|
@@ -1,28 +1,34 @@
|
|
|
1
|
+
from typing import Sequence
|
|
2
|
+
|
|
1
3
|
from web3 import Web3
|
|
2
4
|
from web3.constants import HASH_ZERO
|
|
3
5
|
|
|
4
6
|
from prediction_market_agent_tooling.config import APIKeys
|
|
5
|
-
from prediction_market_agent_tooling.gtypes import
|
|
7
|
+
from prediction_market_agent_tooling.gtypes import (
|
|
8
|
+
ChecksumAddress,
|
|
9
|
+
HexBytes,
|
|
10
|
+
HexStr,
|
|
11
|
+
OutcomeStr,
|
|
12
|
+
)
|
|
6
13
|
from prediction_market_agent_tooling.loggers import logger
|
|
7
|
-
from prediction_market_agent_tooling.markets.agent_market import
|
|
14
|
+
from prediction_market_agent_tooling.markets.agent_market import ProcessedMarket
|
|
8
15
|
from prediction_market_agent_tooling.markets.omen.data_models import (
|
|
9
16
|
ContractPrediction,
|
|
10
17
|
IPFSAgentResult,
|
|
11
18
|
)
|
|
12
19
|
from prediction_market_agent_tooling.markets.omen.omen_contracts import (
|
|
13
|
-
|
|
20
|
+
_AgentResultMappingContract,
|
|
14
21
|
)
|
|
15
22
|
from prediction_market_agent_tooling.tools.ipfs.ipfs_handler import IPFSHandler
|
|
16
23
|
from prediction_market_agent_tooling.tools.utils import BPS_CONSTANT
|
|
17
24
|
from prediction_market_agent_tooling.tools.web3_utils import ipfscidv0_to_byte32
|
|
18
25
|
|
|
19
|
-
# max uint16 for easy prediction identification (if market does not have YES outcome)
|
|
20
|
-
UINT16_MAX = 2**16 - 1 # = 65535
|
|
21
|
-
|
|
22
26
|
|
|
23
27
|
def store_trades(
|
|
24
|
-
|
|
25
|
-
|
|
28
|
+
contract: _AgentResultMappingContract,
|
|
29
|
+
market_id: ChecksumAddress,
|
|
30
|
+
outcomes: Sequence[OutcomeStr],
|
|
31
|
+
traded_market: ProcessedMarket | None,
|
|
26
32
|
keys: APIKeys,
|
|
27
33
|
agent_name: str,
|
|
28
34
|
web3: Web3 | None = None,
|
|
@@ -31,10 +37,18 @@ def store_trades(
|
|
|
31
37
|
logger.warning(f"No prediction for market {market_id}, not storing anything.")
|
|
32
38
|
return None
|
|
33
39
|
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
40
|
+
logger.info(
|
|
41
|
+
f"Storing trades for market {market_id}, with outcomes {outcomes}, {traded_market=}."
|
|
42
|
+
)
|
|
43
|
+
|
|
44
|
+
probabilities = traded_market.answer.probabilities
|
|
45
|
+
if not probabilities:
|
|
46
|
+
logger.info("Skipping this since no probabilities available.")
|
|
37
47
|
return None
|
|
48
|
+
|
|
49
|
+
if all(outcome not in probabilities for outcome in outcomes):
|
|
50
|
+
raise ValueError("No of the market's outcomes is in the probabilities.")
|
|
51
|
+
|
|
38
52
|
reasoning = traded_market.answer.reasoning if traded_market.answer.reasoning else ""
|
|
39
53
|
|
|
40
54
|
ipfs_hash_decoded = HexBytes(HASH_ZERO)
|
|
@@ -46,24 +60,28 @@ def store_trades(
|
|
|
46
60
|
ipfs_hash_decoded = ipfscidv0_to_byte32(ipfs_hash)
|
|
47
61
|
|
|
48
62
|
# tx_hashes must be list of bytes32 (see Solidity contract).
|
|
49
|
-
tx_hashes = [
|
|
50
|
-
HexBytes(HexStr(i.id)) for i in traded_market.trades if i.id is not None
|
|
51
|
-
]
|
|
63
|
+
tx_hashes = [HexBytes(HexStr(i.id)) for i in traded_market.trades]
|
|
52
64
|
|
|
53
|
-
|
|
65
|
+
# Dune dashboard expects the probs to be in the same order as on the market.
|
|
66
|
+
probabilities_converted = [
|
|
67
|
+
(outcome, int(probabilities.get(outcome, 0) * BPS_CONSTANT))
|
|
68
|
+
for outcome in outcomes
|
|
69
|
+
]
|
|
54
70
|
|
|
55
71
|
prediction = ContractPrediction(
|
|
72
|
+
market=market_id,
|
|
56
73
|
publisher=keys.bet_from_address,
|
|
57
74
|
ipfs_hash=ipfs_hash_decoded,
|
|
58
75
|
tx_hashes=tx_hashes,
|
|
59
|
-
|
|
76
|
+
outcomes=[x[0] for x in probabilities_converted],
|
|
77
|
+
estimated_probabilities_bps=[x[1] for x in probabilities_converted],
|
|
60
78
|
)
|
|
61
|
-
tx_receipt =
|
|
79
|
+
tx_receipt = contract.add_prediction(
|
|
62
80
|
api_keys=keys,
|
|
63
|
-
market_address=
|
|
81
|
+
market_address=market_id,
|
|
64
82
|
prediction=prediction,
|
|
65
83
|
web3=web3,
|
|
66
84
|
)
|
|
67
85
|
logger.info(
|
|
68
|
-
f"Added prediction to market {market_id}. - receipt {tx_receipt['transactionHash'].
|
|
86
|
+
f"Added prediction to market {market_id}. - receipt {tx_receipt['transactionHash'].to_0x_hex()}."
|
|
69
87
|
)
|