-
Notifications
You must be signed in to change notification settings - Fork 9
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Enable monitoring of Omen market #59
Changes from 26 commits
28cba51
23c71cc
dd0fc99
fb0d26c
6b45ea5
2100bfc
29f67d9
3a528d0
facd307
c793a7a
b108f00
5a4cfc4
e2bd74d
c8c0ded
d6143fd
80e64c4
e99b12e
9c0dc14
63f689f
818e3f0
ccbc7b2
4d54733
55e9b84
e7dd8a8
505ab29
7973825
e2417d4
8d606ce
692a96e
52f3b99
2c22491
29f8746
6a7bebc
4cccb0b
9a9be8b
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -8,7 +8,6 @@ | |
from prediction_market_agent_tooling.markets.data_models import ( | ||
BetAmount, | ||
Currency, | ||
Resolution, | ||
ResolvedBet, | ||
) | ||
from prediction_market_agent_tooling.markets.manifold.data_models import ( | ||
|
@@ -26,30 +25,54 @@ | |
Note: There is an existing wrapper here: https://github.com/vluzko/manifoldpy. Consider using that instead. | ||
""" | ||
|
||
MARKETS_LIMIT = 1000 # Manifold will only return up to 1000 markets | ||
|
||
|
||
def get_manifold_binary_markets( | ||
limit: int, | ||
term: str = "", | ||
topic_slug: t.Optional[str] = None, | ||
sort: str = "liquidity", | ||
sort: t.Literal["liquidity", "score", "newest", "close-date"] = "liquidity", | ||
filter_: t.Literal[ | ||
"open", "closed", "resolved", "closing-this-month", "closing-next-month" | ||
] = "open", | ||
created_after: t.Optional[datetime] = None, | ||
) -> list[ManifoldMarket]: | ||
all_markets: list[ManifoldMarket] = [] | ||
|
||
url = "https://api.manifold.markets/v0/search-markets" | ||
params: dict[str, t.Union[str, int, float]] = { | ||
"term": term, | ||
"sort": sort, | ||
"limit": limit, | ||
"filter": "open", | ||
"filter": filter_, | ||
"limit": min(limit, MARKETS_LIMIT), | ||
"contractType": "BINARY", | ||
} | ||
if topic_slug: | ||
params["topicSlug"] = topic_slug | ||
response = requests.get(url, params=params) | ||
|
||
response.raise_for_status() | ||
data = response.json() | ||
offset = 0 | ||
while True: | ||
params["offset"] = offset | ||
response = requests.get(url, params=params) | ||
response.raise_for_status() | ||
data = response.json() | ||
markets = [ManifoldMarket.model_validate(x) for x in data] | ||
|
||
if not markets: | ||
break | ||
|
||
for market in markets: | ||
if created_after and market.createdTime < created_after: | ||
continue | ||
all_markets.append(market) | ||
|
||
if len(all_markets) >= limit: | ||
break | ||
|
||
offset += len(markets) | ||
|
||
markets = [ManifoldMarket.model_validate(x) for x in data] | ||
return markets | ||
return all_markets[:limit] | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I know There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Haha ohhhhh I thought it was nice to get merge the 3 functions into 1. Makes sense to remove There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I was worried that if we do If we implement That's why I liked the previous split that was based on the API limitations. However, if you don't think that's a real problem, maybe I'm just too worried so feel free to leave it like this. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yeah good point. I guess I'm saying we can leave it up to the user of
Running a few times:
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The enhancements to the Consider adding error handling for the API requests to gracefully handle failures and provide meaningful feedback to the caller. |
||
|
||
|
||
def pick_binary_market() -> ManifoldMarket: | ||
|
@@ -137,7 +160,7 @@ def manifold_to_generic_resolved_bet(bet: ManifoldBet) -> ResolvedBet: | |
if not market.resolutionTime: | ||
raise ValueError(f"Market {market.id} has no resolution time.") | ||
|
||
market_outcome = market.get_resolution_enum() == Resolution.YES | ||
market_outcome = market.get_resolved_boolean_outcome() | ||
return ResolvedBet( | ||
amount=BetAmount(amount=bet.amount, currency=Currency.Mana), | ||
outcome=bet.get_resolved_boolean_outcome(), | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -30,22 +30,22 @@ class ManifoldMarket(BaseModel): | |
creatorId: str | ||
closeTime: datetime | ||
createdTime: datetime | ||
creatorAvatarUrl: str | ||
creatorAvatarUrl: t.Optional[str] = None | ||
creatorName: str | ||
creatorUsername: str | ||
isResolved: bool | ||
resolution: t.Optional[str] = None | ||
resolution: t.Optional[Resolution] = None | ||
resolutionTime: t.Optional[datetime] = None | ||
lastBetTime: t.Optional[datetime] = None | ||
lastCommentTime: t.Optional[datetime] = None | ||
lastUpdatedTime: datetime | ||
mechanism: str | ||
outcomeType: str | ||
p: float | ||
p: t.Optional[float] = None | ||
pool: ManifoldPool | ||
probability: Probability | ||
slug: str | ||
totalLiquidity: Mana | ||
totalLiquidity: t.Optional[Mana] = None | ||
uniqueBettorCount: int | ||
url: str | ||
volume: Mana | ||
|
@@ -55,14 +55,19 @@ class ManifoldMarket(BaseModel): | |
def outcomes(self) -> list[str]: | ||
return list(self.pool.model_fields.keys()) | ||
|
||
def get_resolution_enum(self) -> Resolution: | ||
return Resolution(self.resolution) | ||
def get_resolved_boolean_outcome(self) -> bool: | ||
if self.resolution == Resolution.YES: | ||
return True | ||
elif self.resolution == Resolution.NO: | ||
return False | ||
else: | ||
should_not_happen(f"Unexpected bet outcome string, '{self.resolution}'.") | ||
|
||
def is_resolved_non_cancelled(self) -> bool: | ||
return ( | ||
self.isResolved | ||
and self.resolutionTime is not None | ||
and self.get_resolution_enum() != Resolution.CANCEL | ||
and self.resolution not in [Resolution.CANCEL, Resolution.MKT] | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We you able to find some information about what MKT means? A quick google search didn't tell me anything relevant, so idk how we should process them. But probably good to just leave it as it is, just checking if you have more information. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It's not clear from the manifold api docs, but I had a look through examples and it seems to be a binary market that doesn't resolve to yes or no, but to a percentage. The condition for this seems to be something the creator of the market can decide. Here's a list of some manifold markets that have resolved this way: There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It's not clear to me what happens to the bets made on a binary market that resolves to MKT |
||
) | ||
|
||
def __repr__(self) -> str: | ||
|
@@ -139,16 +144,15 @@ class ManifoldBet(BaseModel): | |
orderAmount: t.Optional[Mana] = None | ||
fills: t.Optional[list[ManifoldBetFills]] = None | ||
createdTime: datetime | ||
outcome: str | ||
outcome: Resolution | ||
|
||
def get_resolved_boolean_outcome(self) -> bool: | ||
outcome = Resolution(self.outcome) | ||
if outcome == Resolution.YES: | ||
if self.outcome == Resolution.YES: | ||
return True | ||
elif outcome == Resolution.NO: | ||
elif self.outcome == Resolution.NO: | ||
return False | ||
else: | ||
should_not_happen(f"Unexpected bet outcome string, '{outcome.value}'.") | ||
should_not_happen(f"Unexpected bet outcome string, '{self.outcome.value}'.") | ||
|
||
def get_profit(self, market_outcome: bool) -> ProfitAmount: | ||
profit = ( | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There is
manifold_to_generic_resolved_bet
that usesis_resolved_non_cancelled
and then doesmarket_outcome = market.get_resolution_enum() == Resolution.YES
, but this MKT is not accounted for.Maybe we could use pattern from benchmark, where the API's model has
and then that's converted to
in
probable_resolution
? So we don't have to worry about other potential changes.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hmm yeah it's a little messy. I have tidied this a bit now. I feel like we essentially do have that pattern now:
The 'cancellable resolution':
and the boolean yes/no resolution:
Also good spot, have accounted for
Resolution.MKT
cases now.