prediction-market-agent-tooling 0.64.12.dev660__py3-none-any.whl → 0.65.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- prediction_market_agent_tooling/benchmark/agents.py +19 -16
- prediction_market_agent_tooling/benchmark/benchmark.py +94 -84
- prediction_market_agent_tooling/benchmark/utils.py +8 -9
- prediction_market_agent_tooling/deploy/agent.py +85 -125
- prediction_market_agent_tooling/deploy/agent_example.py +20 -10
- prediction_market_agent_tooling/deploy/betting_strategy.py +222 -96
- prediction_market_agent_tooling/deploy/constants.py +4 -0
- prediction_market_agent_tooling/jobs/jobs_models.py +15 -4
- prediction_market_agent_tooling/jobs/omen/omen_jobs.py +3 -3
- prediction_market_agent_tooling/markets/agent_market.py +145 -50
- prediction_market_agent_tooling/markets/blockchain_utils.py +10 -1
- prediction_market_agent_tooling/markets/data_models.py +83 -17
- prediction_market_agent_tooling/markets/manifold/api.py +18 -7
- prediction_market_agent_tooling/markets/manifold/data_models.py +23 -16
- prediction_market_agent_tooling/markets/manifold/manifold.py +18 -18
- prediction_market_agent_tooling/markets/manifold/utils.py +7 -12
- prediction_market_agent_tooling/markets/markets.py +2 -1
- prediction_market_agent_tooling/markets/metaculus/metaculus.py +29 -4
- prediction_market_agent_tooling/markets/omen/data_models.py +17 -32
- prediction_market_agent_tooling/markets/omen/omen.py +65 -108
- prediction_market_agent_tooling/markets/omen/omen_contracts.py +2 -5
- prediction_market_agent_tooling/markets/omen/omen_resolving.py +13 -13
- prediction_market_agent_tooling/markets/omen/omen_subgraph_handler.py +18 -12
- prediction_market_agent_tooling/markets/polymarket/data_models.py +7 -3
- prediction_market_agent_tooling/markets/polymarket/data_models_web.py +7 -3
- prediction_market_agent_tooling/markets/polymarket/polymarket.py +5 -4
- prediction_market_agent_tooling/markets/seer/data_models.py +0 -83
- prediction_market_agent_tooling/markets/seer/price_manager.py +44 -30
- prediction_market_agent_tooling/markets/seer/seer.py +105 -105
- prediction_market_agent_tooling/markets/seer/seer_subgraph_handler.py +34 -41
- prediction_market_agent_tooling/tools/betting_strategies/kelly_criterion.py +1 -1
- prediction_market_agent_tooling/tools/cow/cow_order.py +10 -3
- prediction_market_agent_tooling/tools/is_predictable.py +2 -3
- prediction_market_agent_tooling/tools/langfuse_client_utils.py +4 -4
- prediction_market_agent_tooling/tools/omen/sell_positions.py +3 -2
- prediction_market_agent_tooling/tools/utils.py +26 -13
- {prediction_market_agent_tooling-0.64.12.dev660.dist-info → prediction_market_agent_tooling-0.65.0.dist-info}/METADATA +2 -2
- {prediction_market_agent_tooling-0.64.12.dev660.dist-info → prediction_market_agent_tooling-0.65.0.dist-info}/RECORD +41 -51
- prediction_market_agent_tooling/monitor/financial_metrics/financial_metrics.py +0 -68
- prediction_market_agent_tooling/monitor/markets/manifold.py +0 -90
- prediction_market_agent_tooling/monitor/markets/metaculus.py +0 -43
- prediction_market_agent_tooling/monitor/markets/omen.py +0 -88
- prediction_market_agent_tooling/monitor/markets/polymarket.py +0 -49
- prediction_market_agent_tooling/monitor/monitor.py +0 -406
- prediction_market_agent_tooling/monitor/monitor_app.py +0 -149
- prediction_market_agent_tooling/monitor/monitor_settings.py +0 -27
- prediction_market_agent_tooling/tools/betting_strategies/market_moving.py +0 -146
- prediction_market_agent_tooling/tools/betting_strategies/minimum_bet_to_win.py +0 -12
- {prediction_market_agent_tooling-0.64.12.dev660.dist-info → prediction_market_agent_tooling-0.65.0.dist-info}/LICENSE +0 -0
- {prediction_market_agent_tooling-0.64.12.dev660.dist-info → prediction_market_agent_tooling-0.65.0.dist-info}/WHEEL +0 -0
- {prediction_market_agent_tooling-0.64.12.dev660.dist-info → prediction_market_agent_tooling-0.65.0.dist-info}/entry_points.txt +0 -0
@@ -181,19 +181,23 @@ class Market(BaseModel):
|
|
181
181
|
zip(self.outcomes, self.outcomePrices)
|
182
182
|
)
|
183
183
|
|
184
|
-
# On Polymarket, we can find out binary market resolution by
|
184
|
+
# On Polymarket, we can find out binary market resolution by checking for the outcome prices.
|
185
185
|
# E.g. if `Yes` price (probability) is 1$ and `No` price (probability) is 0$, it means the resolution is `Yes`.
|
186
186
|
if (
|
187
187
|
outcome_to_outcome_price[POLYMARKET_TRUE_OUTCOME] == 1.0
|
188
188
|
and outcome_to_outcome_price[POLYMARKET_FALSE_OUTCOME] == 0.0
|
189
189
|
):
|
190
|
-
return Resolution
|
190
|
+
return Resolution(
|
191
|
+
outcome=OutcomeStr(POLYMARKET_TRUE_OUTCOME), invalid=False
|
192
|
+
)
|
191
193
|
|
192
194
|
elif (
|
193
195
|
outcome_to_outcome_price[POLYMARKET_TRUE_OUTCOME] == 0.0
|
194
196
|
and outcome_to_outcome_price[POLYMARKET_FALSE_OUTCOME] == 1.0
|
195
197
|
):
|
196
|
-
return Resolution
|
198
|
+
return Resolution(
|
199
|
+
outcome=OutcomeStr(POLYMARKET_FALSE_OUTCOME), invalid=False
|
200
|
+
)
|
197
201
|
|
198
202
|
else:
|
199
203
|
raise ValueError(
|
@@ -1,6 +1,6 @@
|
|
1
1
|
import typing as t
|
2
2
|
|
3
|
-
from prediction_market_agent_tooling.gtypes import USD, CollateralToken
|
3
|
+
from prediction_market_agent_tooling.gtypes import USD, CollateralToken, OutcomeStr
|
4
4
|
from prediction_market_agent_tooling.markets.agent_market import (
|
5
5
|
AgentMarket,
|
6
6
|
FilterBy,
|
@@ -40,27 +40,28 @@ class PolymarketAgentMarket(AgentMarket):
|
|
40
40
|
description=model.description,
|
41
41
|
outcomes=[x.outcome for x in model.tokens],
|
42
42
|
resolution=model.resolution,
|
43
|
-
current_p_yes=model.p_yes,
|
44
43
|
created_time=None,
|
45
44
|
close_time=model.end_date_iso,
|
46
45
|
url=model.url,
|
47
46
|
volume=None,
|
48
47
|
outcome_token_pool=None,
|
48
|
+
probabilities={}, # ToDo - Implement when fixing Polymarket
|
49
49
|
)
|
50
50
|
|
51
51
|
def get_tiny_bet_amount(self) -> CollateralToken:
|
52
52
|
raise NotImplementedError("TODO: Implement to allow betting on Polymarket.")
|
53
53
|
|
54
|
-
def place_bet(self, outcome:
|
54
|
+
def place_bet(self, outcome: OutcomeStr, amount: USD) -> str:
|
55
55
|
raise NotImplementedError("TODO: Implement to allow betting on Polymarket.")
|
56
56
|
|
57
57
|
@staticmethod
|
58
|
-
def
|
58
|
+
def get_markets(
|
59
59
|
limit: int,
|
60
60
|
sort_by: SortBy = SortBy.NONE,
|
61
61
|
filter_by: FilterBy = FilterBy.OPEN,
|
62
62
|
created_after: t.Optional[DatetimeUTC] = None,
|
63
63
|
excluded_questions: set[str] | None = None,
|
64
|
+
fetch_categorical_markets: bool = False,
|
64
65
|
) -> t.Sequence["PolymarketAgentMarket"]:
|
65
66
|
if sort_by != SortBy.NONE:
|
66
67
|
raise ValueError(f"Unsuported sort_by {sort_by} for Polymarket.")
|
@@ -1,6 +1,4 @@
|
|
1
|
-
import re
|
2
1
|
import typing as t
|
3
|
-
from enum import Enum
|
4
2
|
from urllib.parse import urljoin
|
5
3
|
|
6
4
|
from pydantic import BaseModel, ConfigDict, Field
|
@@ -16,7 +14,6 @@ from prediction_market_agent_tooling.gtypes import (
|
|
16
14
|
OutcomeWei,
|
17
15
|
Web3Wei,
|
18
16
|
)
|
19
|
-
from prediction_market_agent_tooling.markets.data_models import Resolution
|
20
17
|
from prediction_market_agent_tooling.markets.seer.subgraph_data_models import (
|
21
18
|
SeerParentMarket,
|
22
19
|
)
|
@@ -47,44 +44,6 @@ class CreateCategoricalMarketsParams(BaseModel):
|
|
47
44
|
token_names: list[str] = Field(..., alias="tokenNames")
|
48
45
|
|
49
46
|
|
50
|
-
class SeerOutcomeEnum(str, Enum):
|
51
|
-
YES = "yes"
|
52
|
-
NO = "no"
|
53
|
-
INVALID = "invalid"
|
54
|
-
|
55
|
-
@classmethod
|
56
|
-
def from_bool(cls, value: bool) -> "SeerOutcomeEnum":
|
57
|
-
return cls.YES if value else cls.NO
|
58
|
-
|
59
|
-
@classmethod
|
60
|
-
def from_string(cls, value: str) -> "SeerOutcomeEnum":
|
61
|
-
"""Convert a string (case-insensitive) to an Outcome enum."""
|
62
|
-
normalized = value.strip().lower()
|
63
|
-
patterns = {
|
64
|
-
r"^yes$": cls.YES,
|
65
|
-
r"^no$": cls.NO,
|
66
|
-
r"^(invalid|invalid result)$": cls.INVALID,
|
67
|
-
}
|
68
|
-
|
69
|
-
# Search through patterns and return the first match
|
70
|
-
for pattern, outcome in patterns.items():
|
71
|
-
if re.search(pattern, normalized):
|
72
|
-
return outcome
|
73
|
-
|
74
|
-
raise ValueError(f"Could not map {value=} to an outcome.")
|
75
|
-
|
76
|
-
def to_bool(self) -> bool:
|
77
|
-
"""Convert a SeerOutcomeEnum to a boolean value."""
|
78
|
-
if self == self.YES:
|
79
|
-
return True
|
80
|
-
elif self == self.NO:
|
81
|
-
return False
|
82
|
-
elif self == self.INVALID:
|
83
|
-
raise ValueError("Cannot convert INVALID outcome to boolean.")
|
84
|
-
else:
|
85
|
-
raise ValueError(f"Unknown outcome: {self}")
|
86
|
-
|
87
|
-
|
88
47
|
SEER_BASE_URL = "https://app.seer.pm"
|
89
48
|
|
90
49
|
|
@@ -115,22 +74,8 @@ class SeerMarket(BaseModel):
|
|
115
74
|
# 1. An invalid outcome AND
|
116
75
|
# 2. Invalid payoutNumerator is 1.
|
117
76
|
|
118
|
-
try:
|
119
|
-
self.outcome_as_enums[SeerOutcomeEnum.INVALID]
|
120
|
-
except KeyError:
|
121
|
-
raise ValueError(
|
122
|
-
f"Market {self.id.hex()} has no invalid outcome. {self.outcomes}"
|
123
|
-
)
|
124
|
-
|
125
77
|
return self.payout_reported and self.payout_numerators[-1] != 1
|
126
78
|
|
127
|
-
@property
|
128
|
-
def outcome_as_enums(self) -> dict[SeerOutcomeEnum, int]:
|
129
|
-
return {
|
130
|
-
SeerOutcomeEnum.from_string(outcome): idx
|
131
|
-
for idx, outcome in enumerate(self.outcomes)
|
132
|
-
}
|
133
|
-
|
134
79
|
@property
|
135
80
|
def is_resolved(self) -> bool:
|
136
81
|
return self.payout_reported
|
@@ -139,18 +84,6 @@ class SeerMarket(BaseModel):
|
|
139
84
|
def is_resolved_with_valid_answer(self) -> bool:
|
140
85
|
return self.is_resolved and self.has_valid_answer
|
141
86
|
|
142
|
-
def get_resolution_enum(self) -> t.Optional[Resolution]:
|
143
|
-
if not self.is_resolved_with_valid_answer:
|
144
|
-
return None
|
145
|
-
|
146
|
-
max_idx = self.payout_numerators.index(1)
|
147
|
-
|
148
|
-
outcome: str = self.outcomes[max_idx]
|
149
|
-
outcome_enum = SeerOutcomeEnum.from_string(outcome)
|
150
|
-
if outcome_enum.to_bool():
|
151
|
-
return Resolution.YES
|
152
|
-
return Resolution.NO
|
153
|
-
|
154
87
|
def is_redeemable(self, owner: ChecksumAddress, web3: Web3 | None = None) -> bool:
|
155
88
|
token_balances = self.get_outcome_token_balances(owner, web3)
|
156
89
|
if not self.payout_reported:
|
@@ -177,22 +110,6 @@ class SeerMarket(BaseModel):
|
|
177
110
|
# 3 because Seer has also third, `Invalid` outcome.
|
178
111
|
return len(self.outcomes) == 3
|
179
112
|
|
180
|
-
def boolean_outcome_from_answer(self, answer: HexBytes) -> bool:
|
181
|
-
if not self.is_binary:
|
182
|
-
raise ValueError(
|
183
|
-
f"Market with title {self.title} is not binary, it has {len(self.outcomes)} outcomes."
|
184
|
-
)
|
185
|
-
|
186
|
-
outcome: str = self.outcomes[answer.as_int()]
|
187
|
-
outcome_enum = SeerOutcomeEnum.from_string(outcome)
|
188
|
-
return outcome_enum.to_bool()
|
189
|
-
|
190
|
-
def get_resolution_enum_from_answer(self, answer: HexBytes) -> Resolution:
|
191
|
-
if self.boolean_outcome_from_answer(answer):
|
192
|
-
return Resolution.YES
|
193
|
-
else:
|
194
|
-
return Resolution.NO
|
195
|
-
|
196
113
|
@property
|
197
114
|
def collateral_token_contract_address_checksummed(self) -> ChecksumAddress:
|
198
115
|
return Web3.to_checksum_address(self.collateral_token)
|
@@ -6,13 +6,12 @@ from web3 import Web3
|
|
6
6
|
from prediction_market_agent_tooling.gtypes import (
|
7
7
|
ChecksumAddress,
|
8
8
|
CollateralToken,
|
9
|
+
HexAddress,
|
10
|
+
OutcomeStr,
|
9
11
|
Probability,
|
10
12
|
)
|
11
13
|
from prediction_market_agent_tooling.loggers import logger
|
12
|
-
from prediction_market_agent_tooling.markets.seer.data_models import
|
13
|
-
SeerMarket,
|
14
|
-
SeerOutcomeEnum,
|
15
|
-
)
|
14
|
+
from prediction_market_agent_tooling.markets.seer.data_models import SeerMarket
|
16
15
|
from prediction_market_agent_tooling.markets.seer.seer_subgraph_handler import (
|
17
16
|
SeerSubgraphHandler,
|
18
17
|
)
|
@@ -65,30 +64,6 @@ class PriceManager:
|
|
65
64
|
f"{price_diff_pct=} larger than {max_price_diff=} for seer market {self.seer_market.id.hex()} "
|
66
65
|
)
|
67
66
|
|
68
|
-
def current_p_yes(self) -> Probability | None:
|
69
|
-
# Inspired by https://github.com/seer-pm/demo/blob/ca682153a6b4d4dd3dcc4ad8bdcbe32202fc8fe7/web/src/hooks/useMarketOdds.ts#L15
|
70
|
-
price_data: dict[int, CollateralToken | None] = {}
|
71
|
-
for idx, wrapped_token in enumerate(self.seer_market.wrapped_tokens):
|
72
|
-
price = self.get_price_for_token(
|
73
|
-
token=Web3.to_checksum_address(wrapped_token),
|
74
|
-
)
|
75
|
-
|
76
|
-
price_data[idx] = price
|
77
|
-
|
78
|
-
price_yes = price_data[self.seer_market.outcome_as_enums[SeerOutcomeEnum.YES]]
|
79
|
-
price_no = price_data[self.seer_market.outcome_as_enums[SeerOutcomeEnum.NO]]
|
80
|
-
|
81
|
-
# We only return a probability if we have both price_yes and price_no, since we could place bets
|
82
|
-
# in both sides hence we need current probabilities for both outcomes.
|
83
|
-
if price_yes is not None and price_no is not None:
|
84
|
-
normalized_price_yes = price_yes / (price_yes + price_no)
|
85
|
-
self._log_track_price_normalization_diff(
|
86
|
-
old_price=price_yes.value, normalized_price=normalized_price_yes
|
87
|
-
)
|
88
|
-
return Probability(normalized_price_yes)
|
89
|
-
else:
|
90
|
-
return None
|
91
|
-
|
92
67
|
@cached(TTLCache(maxsize=100, ttl=5 * 60), key=_make_cache_key)
|
93
68
|
def get_price_for_token(
|
94
69
|
self,
|
@@ -117,7 +92,7 @@ class PriceManager:
|
|
117
92
|
return self.get_token_price_from_pools(token=token)
|
118
93
|
|
119
94
|
@staticmethod
|
120
|
-
def
|
95
|
+
def pool_token0_matches_token(token: ChecksumAddress, pool: SeerPool) -> bool:
|
121
96
|
return pool.token0.id.hex().lower() == token.lower()
|
122
97
|
|
123
98
|
def get_token_price_from_pools(
|
@@ -138,7 +113,46 @@ class PriceManager:
|
|
138
113
|
# For example, in a outcomeYES (token0)/sDAI pool (token1), token1Price is the price of outcomeYES in units of sDAI.
|
139
114
|
price = (
|
140
115
|
pool.token1Price
|
141
|
-
if self.
|
116
|
+
if self.pool_token0_matches_token(token=token, pool=pool)
|
142
117
|
else pool.token0Price
|
143
118
|
)
|
144
119
|
return price
|
120
|
+
|
121
|
+
def build_probability_map(self) -> dict[OutcomeStr, Probability]:
|
122
|
+
# Inspired by https://github.com/seer-pm/demo/blob/ca682153a6b4d4dd3dcc4ad8bdcbe32202fc8fe7/web/src/hooks/useMarketOdds.ts#L15
|
123
|
+
price_data: dict[HexAddress, CollateralToken] = {}
|
124
|
+
|
125
|
+
for wrapped_token in self.seer_market.wrapped_tokens:
|
126
|
+
price = self.get_price_for_token(
|
127
|
+
token=Web3.to_checksum_address(wrapped_token),
|
128
|
+
)
|
129
|
+
price_data[wrapped_token] = (
|
130
|
+
price if price is not None else CollateralToken.zero()
|
131
|
+
)
|
132
|
+
|
133
|
+
# We normalize the prices to sum up to 1.
|
134
|
+
normalized_prices = {}
|
135
|
+
|
136
|
+
if not price_data or (
|
137
|
+
sum(price_data.values(), start=CollateralToken.zero())
|
138
|
+
== CollateralToken.zero()
|
139
|
+
):
|
140
|
+
return {
|
141
|
+
OutcomeStr(outcome): Probability(0)
|
142
|
+
for outcome in self.seer_market.outcomes
|
143
|
+
}
|
144
|
+
|
145
|
+
for outcome_token, price in price_data.items():
|
146
|
+
old_price = price
|
147
|
+
new_price = Probability(
|
148
|
+
price / (sum(price_data.values(), start=CollateralToken.zero()))
|
149
|
+
)
|
150
|
+
self._log_track_price_normalization_diff(
|
151
|
+
old_price=old_price.value, normalized_price=new_price
|
152
|
+
)
|
153
|
+
outcome = self.seer_market.outcomes[
|
154
|
+
self.seer_market.wrapped_tokens.index(outcome_token)
|
155
|
+
]
|
156
|
+
normalized_prices[OutcomeStr(outcome)] = new_price
|
157
|
+
|
158
|
+
return normalized_prices
|
@@ -1,6 +1,5 @@
|
|
1
1
|
import typing as t
|
2
2
|
|
3
|
-
from eth_pydantic_types import HexStr
|
4
3
|
from eth_typing import ChecksumAddress
|
5
4
|
from web3 import Web3
|
6
5
|
from web3.types import TxReceipt
|
@@ -11,6 +10,7 @@ from prediction_market_agent_tooling.gtypes import (
|
|
11
10
|
CollateralToken,
|
12
11
|
HexAddress,
|
13
12
|
HexBytes,
|
13
|
+
HexStr,
|
14
14
|
OutcomeStr,
|
15
15
|
OutcomeToken,
|
16
16
|
OutcomeWei,
|
@@ -30,7 +30,6 @@ from prediction_market_agent_tooling.markets.omen.omen import OmenAgentMarket
|
|
30
30
|
from prediction_market_agent_tooling.markets.seer.data_models import (
|
31
31
|
RedeemParams,
|
32
32
|
SeerMarket,
|
33
|
-
SeerOutcomeEnum,
|
34
33
|
)
|
35
34
|
from prediction_market_agent_tooling.markets.seer.price_manager import PriceManager
|
36
35
|
from prediction_market_agent_tooling.markets.seer.seer_contracts import (
|
@@ -71,10 +70,10 @@ class SeerAgentMarket(AgentMarket):
|
|
71
70
|
creator: HexAddress
|
72
71
|
collateral_token_contract_address_checksummed: ChecksumAddress
|
73
72
|
condition_id: HexBytes
|
74
|
-
seer_outcomes: dict[SeerOutcomeEnum, int]
|
75
73
|
description: str | None = (
|
76
74
|
None # Seer markets don't have a description, so just default to None.
|
77
75
|
)
|
76
|
+
outcomes_supply: int
|
78
77
|
|
79
78
|
def get_collateral_token_contract(
|
80
79
|
self, web3: Web3 | None = None
|
@@ -110,12 +109,17 @@ class SeerAgentMarket(AgentMarket):
|
|
110
109
|
return get_usd_in_token(x, self.collateral_token_contract_address_checksummed)
|
111
110
|
|
112
111
|
def get_buy_token_amount(
|
113
|
-
self, bet_amount: USD | CollateralToken,
|
112
|
+
self, bet_amount: USD | CollateralToken, outcome_str: OutcomeStr
|
114
113
|
) -> OutcomeToken | None:
|
115
114
|
"""Returns number of outcome tokens returned for a given bet expressed in collateral units."""
|
116
115
|
|
117
|
-
outcome_str
|
116
|
+
if outcome_str not in self.outcomes:
|
117
|
+
raise ValueError(
|
118
|
+
f"Outcome {outcome_str} not found in market outcomes {self.outcomes}"
|
119
|
+
)
|
120
|
+
|
118
121
|
outcome_token = self.get_wrapped_token_for_outcome(outcome_str)
|
122
|
+
|
119
123
|
bet_amount_in_tokens = self.get_in_token(bet_amount)
|
120
124
|
|
121
125
|
p = PriceManager.build(market_id=HexBytes(HexStr(self.id)))
|
@@ -130,13 +134,12 @@ class SeerAgentMarket(AgentMarket):
|
|
130
134
|
return OutcomeToken(amount_outcome_tokens)
|
131
135
|
|
132
136
|
def get_sell_value_of_outcome_token(
|
133
|
-
self, outcome:
|
137
|
+
self, outcome: OutcomeStr, amount: OutcomeToken
|
134
138
|
) -> CollateralToken:
|
135
139
|
if amount == amount.zero():
|
136
140
|
return CollateralToken.zero()
|
137
141
|
|
138
|
-
|
139
|
-
wrapped_outcome_token = self.wrapped_tokens[outcome_index]
|
142
|
+
wrapped_outcome_token = self.get_wrapped_token_for_outcome(outcome)
|
140
143
|
|
141
144
|
# We calculate how much collateral we would get back if we sold `amount` of outcome token.
|
142
145
|
value_outcome_token_in_collateral = get_buy_token_amount_else_raise(
|
@@ -146,11 +149,6 @@ class SeerAgentMarket(AgentMarket):
|
|
146
149
|
)
|
147
150
|
return value_outcome_token_in_collateral.as_token
|
148
151
|
|
149
|
-
def get_outcome_str_from_bool(self, outcome: bool) -> OutcomeStr:
|
150
|
-
outcome_translated = SeerOutcomeEnum.from_bool(outcome)
|
151
|
-
idx = self.seer_outcomes[outcome_translated]
|
152
|
-
return OutcomeStr(self.outcomes[idx])
|
153
|
-
|
154
152
|
@staticmethod
|
155
153
|
def get_trade_balance(api_keys: APIKeys) -> USD:
|
156
154
|
return OmenAgentMarket.get_trade_balance(api_keys=api_keys)
|
@@ -168,17 +166,16 @@ class SeerAgentMarket(AgentMarket):
|
|
168
166
|
|
169
167
|
amounts_ot: dict[OutcomeStr, OutcomeToken] = {}
|
170
168
|
|
171
|
-
for
|
172
|
-
outcome_str = self.get_outcome_str_from_bool(outcome)
|
173
|
-
wrapped_token = self.get_wrapped_token_for_outcome(outcome_str)
|
174
|
-
|
169
|
+
for outcome_str, wrapped_token in zip(self.outcomes, self.wrapped_tokens):
|
175
170
|
outcome_token_balance_wei = OutcomeWei.from_wei(
|
176
171
|
ContractERC20OnGnosisChain(address=wrapped_token).balanceOf(
|
177
172
|
for_address=Web3.to_checksum_address(user_id), web3=web3
|
178
173
|
)
|
179
174
|
)
|
180
|
-
|
181
|
-
amounts_ot[
|
175
|
+
|
176
|
+
amounts_ot[
|
177
|
+
OutcomeStr(outcome_str)
|
178
|
+
] = outcome_token_balance_wei.as_outcome_token
|
182
179
|
|
183
180
|
amounts_current = {
|
184
181
|
k: self.get_token_in_usd(self.get_sell_value_of_outcome_token(k, v))
|
@@ -194,76 +191,6 @@ class SeerAgentMarket(AgentMarket):
|
|
194
191
|
amounts_ot=amounts_ot,
|
195
192
|
)
|
196
193
|
|
197
|
-
def get_outcome_str_from_idx(self, outcome_index: int) -> OutcomeStr:
|
198
|
-
return self.outcomes[outcome_index]
|
199
|
-
|
200
|
-
def get_liquidity_for_outcome(
|
201
|
-
self, outcome: OutcomeStr, web3: Web3 | None = None
|
202
|
-
) -> CollateralToken:
|
203
|
-
"""Liquidity per outcome is comprised of the balance of outcomeToken + collateralToken held by the pool itself (see https://github.com/seer-pm/demo/blob/7bfd0a062780ed6567f65714c4fc4f6e6cdf1c4f/web/netlify/functions/utils/fetchPools.ts#L35-L42)."""
|
204
|
-
|
205
|
-
outcome_token = self.get_wrapped_token_for_outcome(outcome)
|
206
|
-
pool = SeerSubgraphHandler().get_pool_by_token(
|
207
|
-
token_address=outcome_token,
|
208
|
-
collateral_address=self.collateral_token_contract_address_checksummed,
|
209
|
-
)
|
210
|
-
if not pool:
|
211
|
-
logger.info(
|
212
|
-
f"Could not fetch pool for token {outcome_token}, no liquidity available for outcome."
|
213
|
-
)
|
214
|
-
return CollateralToken(0)
|
215
|
-
p = PriceManager.build(HexBytes(HexStr(self.id)))
|
216
|
-
total = CollateralToken(0)
|
217
|
-
|
218
|
-
for token_address in [pool.token0.id, pool.token1.id]:
|
219
|
-
token_address_checksummed = Web3.to_checksum_address(token_address)
|
220
|
-
token_contract = ContractERC20OnGnosisChain(
|
221
|
-
address=token_address_checksummed
|
222
|
-
)
|
223
|
-
|
224
|
-
token_balance = token_contract.balance_of_in_tokens(
|
225
|
-
for_address=Web3.to_checksum_address(pool.id.hex()),
|
226
|
-
web3=web3,
|
227
|
-
)
|
228
|
-
|
229
|
-
# get price
|
230
|
-
token_price_in_sdai = (
|
231
|
-
p.get_token_price_from_pools(token=token_address_checksummed)
|
232
|
-
if token_address_checksummed
|
233
|
-
!= self.collateral_token_contract_address_checksummed
|
234
|
-
else CollateralToken(1.0)
|
235
|
-
)
|
236
|
-
|
237
|
-
# We ignore the liquidity in outcome tokens if price unknown.
|
238
|
-
if token_price_in_sdai:
|
239
|
-
sdai_balance = token_balance * token_price_in_sdai
|
240
|
-
total += sdai_balance
|
241
|
-
|
242
|
-
return total
|
243
|
-
|
244
|
-
def get_liquidity(self) -> CollateralToken:
|
245
|
-
liquidity_in_collateral = CollateralToken(0)
|
246
|
-
# We ignore the invalid outcome
|
247
|
-
for outcome in self.outcomes[:-1]:
|
248
|
-
liquidity_for_outcome = self.get_liquidity_for_outcome(outcome)
|
249
|
-
liquidity_in_collateral += liquidity_for_outcome
|
250
|
-
|
251
|
-
return liquidity_in_collateral
|
252
|
-
|
253
|
-
def has_liquidity_for_outcome(self, outcome: OutcomeStr) -> bool:
|
254
|
-
liquidity = self.get_liquidity_for_outcome(outcome)
|
255
|
-
return liquidity > CollateralToken(0)
|
256
|
-
|
257
|
-
def has_liquidity(self) -> bool:
|
258
|
-
# We define a market as having liquidity if it has liquidity for all outcomes except for the invalid (index -1)
|
259
|
-
return all(
|
260
|
-
[self.has_liquidity_for_outcome(outcome) for outcome in self.outcomes[:-1]]
|
261
|
-
)
|
262
|
-
|
263
|
-
def get_wrapped_token_for_outcome(self, outcome: OutcomeStr) -> ChecksumAddress:
|
264
|
-
outcome_idx = self.outcomes.index(outcome)
|
265
|
-
return self.wrapped_tokens[outcome_idx]
|
266
|
-
|
267
194
|
def get_position(
|
268
195
|
self, user_id: str, web3: Web3 | None = None
|
269
196
|
) -> ExistingPosition | None:
|
@@ -307,7 +234,7 @@ class SeerAgentMarket(AgentMarket):
|
|
307
234
|
web3 = RPCConfig().get_web3()
|
308
235
|
subgraph = SeerSubgraphHandler()
|
309
236
|
|
310
|
-
closed_markets = subgraph.
|
237
|
+
closed_markets = subgraph.get_markets(
|
311
238
|
filter_by=FilterBy.RESOLVED, sort_by=SortBy.NEWEST
|
312
239
|
)
|
313
240
|
filtered_markets = SeerAgentMarket._filter_markets_contained_in_trades(
|
@@ -351,10 +278,11 @@ class SeerAgentMarket(AgentMarket):
|
|
351
278
|
model: SeerMarket, seer_subgraph: SeerSubgraphHandler
|
352
279
|
) -> t.Optional["SeerAgentMarket"]:
|
353
280
|
p = PriceManager(seer_market=model, seer_subgraph=seer_subgraph)
|
354
|
-
|
355
|
-
|
281
|
+
|
282
|
+
probability_map = p.build_probability_map()
|
283
|
+
if not probability_map:
|
356
284
|
logger.info(
|
357
|
-
f"
|
285
|
+
f"probability_map for market {model.id.hex()} could not be calculated. Skipping."
|
358
286
|
)
|
359
287
|
return None
|
360
288
|
|
@@ -371,23 +299,27 @@ class SeerAgentMarket(AgentMarket):
|
|
371
299
|
wrapped_tokens=[Web3.to_checksum_address(i) for i in model.wrapped_tokens],
|
372
300
|
fees=MarketFees.get_zero_fees(),
|
373
301
|
outcome_token_pool=None,
|
374
|
-
|
302
|
+
outcomes_supply=model.outcomes_supply,
|
303
|
+
resolution=None,
|
375
304
|
volume=None,
|
376
|
-
|
377
|
-
seer_outcomes=model.outcome_as_enums,
|
305
|
+
probabilities=probability_map,
|
378
306
|
)
|
379
307
|
|
380
308
|
@staticmethod
|
381
|
-
def
|
309
|
+
def get_markets(
|
382
310
|
limit: int,
|
383
311
|
sort_by: SortBy,
|
384
312
|
filter_by: FilterBy = FilterBy.OPEN,
|
385
313
|
created_after: t.Optional[DatetimeUTC] = None,
|
386
314
|
excluded_questions: set[str] | None = None,
|
315
|
+
fetch_categorical_markets: bool = False,
|
387
316
|
) -> t.Sequence["SeerAgentMarket"]:
|
388
317
|
seer_subgraph = SeerSubgraphHandler()
|
389
|
-
markets = seer_subgraph.
|
390
|
-
limit=limit,
|
318
|
+
markets = seer_subgraph.get_markets(
|
319
|
+
limit=limit,
|
320
|
+
sort_by=sort_by,
|
321
|
+
filter_by=filter_by,
|
322
|
+
include_categorical_markets=fetch_categorical_markets,
|
391
323
|
)
|
392
324
|
|
393
325
|
# We exclude the None values below because `from_data_model_with_subgraph` can return None, which
|
@@ -403,9 +335,79 @@ class SeerAgentMarket(AgentMarket):
|
|
403
335
|
is not None
|
404
336
|
]
|
405
337
|
|
338
|
+
def get_outcome_str_from_idx(self, outcome_index: int) -> OutcomeStr:
|
339
|
+
return self.outcomes[outcome_index]
|
340
|
+
|
341
|
+
def get_liquidity_for_outcome(
|
342
|
+
self, outcome: OutcomeStr, web3: Web3 | None = None
|
343
|
+
) -> CollateralToken:
|
344
|
+
"""Liquidity per outcome is comprised of the balance of outcomeToken + collateralToken held by the pool itself (see https://github.com/seer-pm/demo/blob/7bfd0a062780ed6567f65714c4fc4f6e6cdf1c4f/web/netlify/functions/utils/fetchPools.ts#L35-L42)."""
|
345
|
+
|
346
|
+
outcome_token = self.get_wrapped_token_for_outcome(outcome)
|
347
|
+
pool = SeerSubgraphHandler().get_pool_by_token(
|
348
|
+
token_address=outcome_token,
|
349
|
+
collateral_address=self.collateral_token_contract_address_checksummed,
|
350
|
+
)
|
351
|
+
if not pool:
|
352
|
+
logger.info(
|
353
|
+
f"Could not fetch pool for token {outcome_token}, no liquidity available for outcome."
|
354
|
+
)
|
355
|
+
return CollateralToken(0)
|
356
|
+
p = PriceManager.build(HexBytes(HexStr(self.id)))
|
357
|
+
total = CollateralToken(0)
|
358
|
+
|
359
|
+
for token_address in [pool.token0.id, pool.token1.id]:
|
360
|
+
token_address_checksummed = Web3.to_checksum_address(token_address)
|
361
|
+
token_contract = ContractERC20OnGnosisChain(
|
362
|
+
address=token_address_checksummed
|
363
|
+
)
|
364
|
+
|
365
|
+
token_balance = token_contract.balance_of_in_tokens(
|
366
|
+
for_address=Web3.to_checksum_address(HexAddress(HexStr(pool.id.hex()))),
|
367
|
+
web3=web3,
|
368
|
+
)
|
369
|
+
|
370
|
+
# get price
|
371
|
+
token_price_in_sdai = (
|
372
|
+
p.get_token_price_from_pools(token=token_address_checksummed)
|
373
|
+
if token_address_checksummed
|
374
|
+
!= self.collateral_token_contract_address_checksummed
|
375
|
+
else CollateralToken(1.0)
|
376
|
+
)
|
377
|
+
|
378
|
+
# We ignore the liquidity in outcome tokens if price unknown.
|
379
|
+
if token_price_in_sdai:
|
380
|
+
sdai_balance = token_balance * token_price_in_sdai
|
381
|
+
total += sdai_balance
|
382
|
+
|
383
|
+
return total
|
384
|
+
|
385
|
+
def get_liquidity(self) -> CollateralToken:
|
386
|
+
liquidity_in_collateral = CollateralToken(0)
|
387
|
+
# We ignore the invalid outcome
|
388
|
+
for outcome in self.outcomes[:-1]:
|
389
|
+
liquidity_for_outcome = self.get_liquidity_for_outcome(outcome)
|
390
|
+
liquidity_in_collateral += liquidity_for_outcome
|
391
|
+
|
392
|
+
return liquidity_in_collateral
|
393
|
+
|
394
|
+
def has_liquidity_for_outcome(self, outcome: OutcomeStr) -> bool:
|
395
|
+
liquidity = self.get_liquidity_for_outcome(outcome)
|
396
|
+
return liquidity > CollateralToken(0)
|
397
|
+
|
398
|
+
def has_liquidity(self) -> bool:
|
399
|
+
# We define a market as having liquidity if it has liquidity for all outcomes except for the invalid (index -1)
|
400
|
+
return all(
|
401
|
+
[self.has_liquidity_for_outcome(outcome) for outcome in self.outcomes[:-1]]
|
402
|
+
)
|
403
|
+
|
404
|
+
def get_wrapped_token_for_outcome(self, outcome: OutcomeStr) -> ChecksumAddress:
|
405
|
+
outcome_idx = self.outcomes.index(outcome)
|
406
|
+
return self.wrapped_tokens[outcome_idx]
|
407
|
+
|
406
408
|
def place_bet(
|
407
409
|
self,
|
408
|
-
outcome:
|
410
|
+
outcome: OutcomeStr,
|
409
411
|
amount: USD,
|
410
412
|
auto_deposit: bool = True,
|
411
413
|
web3: Web3 | None = None,
|
@@ -432,8 +434,7 @@ class SeerAgentMarket(AgentMarket):
|
|
432
434
|
f"Balance {collateral_balance} not enough for bet size {amount}"
|
433
435
|
)
|
434
436
|
|
435
|
-
|
436
|
-
outcome_token = self.get_wrapped_token_for_outcome(outcome_str)
|
437
|
+
outcome_token = self.get_wrapped_token_for_outcome(outcome)
|
437
438
|
|
438
439
|
# Sell sDAI using token address
|
439
440
|
order_metadata = swap_tokens_waiting(
|
@@ -451,7 +452,7 @@ class SeerAgentMarket(AgentMarket):
|
|
451
452
|
|
452
453
|
def sell_tokens(
|
453
454
|
self,
|
454
|
-
outcome:
|
455
|
+
outcome: OutcomeStr,
|
455
456
|
amount: USD | OutcomeToken,
|
456
457
|
auto_withdraw: bool = True,
|
457
458
|
api_keys: APIKeys | None = None,
|
@@ -460,8 +461,7 @@ class SeerAgentMarket(AgentMarket):
|
|
460
461
|
"""
|
461
462
|
Sells the given number of shares for the given outcome in the given market.
|
462
463
|
"""
|
463
|
-
|
464
|
-
outcome_token = self.get_wrapped_token_for_outcome(outcome_str)
|
464
|
+
outcome_token = self.get_wrapped_token_for_outcome(outcome)
|
465
465
|
api_keys = api_keys if api_keys is not None else APIKeys()
|
466
466
|
|
467
467
|
token_amount = (
|