Skip to content

Commit

Permalink
Add outcome_token_pool to AgentMarket (#386)
Browse files Browse the repository at this point in the history
  • Loading branch information
evangriffiths authored Sep 9, 2024
1 parent 81affd1 commit 429e1a8
Show file tree
Hide file tree
Showing 12 changed files with 234 additions and 104 deletions.
1 change: 1 addition & 0 deletions .github/workflows/python_ci.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ env:
GCP_SA_KEY: ${{ secrets.GCP_SA_KEY }}
GNOSIS_RPC_URL: ${{ secrets.GNOSIS_RPC_URL }}
GRAPH_API_KEY: ${{ secrets.GRAPH_API_KEY }}
METACULUS_API_KEY: ${{ secrets.METACULUS_API_KEY }}

jobs:
mypy:
Expand Down
209 changes: 105 additions & 104 deletions poetry.lock

Large diffs are not rendered by default.

29 changes: 29 additions & 0 deletions prediction_market_agent_tooling/markets/agent_market.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

from eth_typing import ChecksumAddress
from pydantic import BaseModel, field_validator
from pydantic_core.core_schema import FieldValidationInfo

from prediction_market_agent_tooling.config import APIKeys
from prediction_market_agent_tooling.gtypes import Probability
Expand Down Expand Up @@ -49,6 +50,9 @@ class AgentMarket(BaseModel):
question: str
description: str | None
outcomes: list[str]
outcome_token_pool: dict[
str, float
] | None # Should be in currency of `currency` above.
resolution: Resolution | None
created_time: datetime | None
close_time: datetime | None
Expand All @@ -63,6 +67,22 @@ class AgentMarket(BaseModel):
add_utc_timezone_validator
)

@field_validator("outcome_token_pool")
def validate_outcome_token_pool(
cls,
outcome_token_pool: dict[str, float] | None,
info: FieldValidationInfo,
) -> dict[str, float] | None:
outcomes: list[str] = check_not_none(info.data.get("outcomes"))
if outcome_token_pool is not None:
outcome_keys = set(outcome_token_pool.keys())
expected_keys = set(outcomes)
if outcome_keys != expected_keys:
raise ValueError(
f"Keys of outcome_token_pool ({outcome_keys}) do not match outcomes ({expected_keys})."
)
return outcome_token_pool

@property
def current_p_no(self) -> Probability:
return Probability(1 - self.current_p_yes)
Expand Down Expand Up @@ -239,3 +259,12 @@ def can_be_traded(self) -> bool:
@classmethod
def get_user_url(cls, keys: APIKeys) -> str:
raise NotImplementedError("Subclasses must implement this method")

def has_token_pool(self) -> bool:
return self.outcome_token_pool is not None

def get_pool_tokens(self, outcome: str) -> float:
if not self.outcome_token_pool:
raise ValueError("Outcome token pool is not available.")

return self.outcome_token_pool[outcome]
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,12 @@ class ManifoldPool(BaseModel):
NO: float
YES: float

def size_for_outcome(self, outcome: str) -> float:
if hasattr(self, outcome):
return float(getattr(self, outcome))
else:
should_not_happen(f"Unexpected outcome string, '{outcome}'.")


class ManifoldAnswersMode(str, Enum):
ANYONE = "ANYONE"
Expand Down
3 changes: 3 additions & 0 deletions prediction_market_agent_tooling/markets/manifold/manifold.py
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,9 @@ def from_data_model(model: FullManifoldMarket) -> "ManifoldAgentMarket":
current_p_yes=model.probability,
url=model.url,
volume=model.volume,
outcome_token_pool={
o: model.pool.size_for_outcome(o) for o in model.outcomes
},
)

@staticmethod
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ def from_data_model(model: MetaculusQuestion) -> "MetaculusAgentMarket":
volume=None,
have_predicted=model.my_predictions is not None
and len(model.my_predictions.predictions) > 0,
outcome_token_pool=None,
)

@staticmethod
Expand Down
4 changes: 4 additions & 0 deletions prediction_market_agent_tooling/markets/omen/omen.py
Original file line number Diff line number Diff line change
Expand Up @@ -345,6 +345,10 @@ def from_data_model(model: OmenMarket) -> "OmenAgentMarket":
volume=wei_to_xdai(model.collateralVolume),
close_time=model.close_time,
fee=float(wei_to_xdai(model.fee)) if model.fee is not None else 0.0,
outcome_token_pool={
model.outcomes[i]: wei_to_xdai(Wei(model.outcomeTokenAmounts[i]))
for i in range(len(model.outcomes))
},
)

@staticmethod
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ def from_data_model(model: PolymarketMarketWithPrices) -> "PolymarketAgentMarket
close_time=model.end_date_iso,
url=model.url,
volume=None,
outcome_token_pool=None,
)

@classmethod
Expand Down
2 changes: 2 additions & 0 deletions tests/markets/test_betting_strategies.py
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,7 @@ def test_minimum_bet_to_win(
volume=None,
finalized_time=None,
fee=0.02,
outcome_token_pool=None,
),
)
assert (
Expand Down Expand Up @@ -151,6 +152,7 @@ def test_minimum_bet_to_win_manifold(
resolution=None,
url="url",
volume=None,
outcome_token_pool=None,
).get_minimum_bet_to_win(outcome, amount_to_win)
assert min_bet == expected_min_bet, f"Expected {expected_min_bet}, got {min_bet}."

Expand Down
11 changes: 11 additions & 0 deletions tests/markets/test_manifold.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
manifold_to_generic_resolved_bet,
place_bet,
)
from prediction_market_agent_tooling.markets.manifold.data_models import ManifoldPool
from tests.utils import RUN_PAID_TESTS


Expand Down Expand Up @@ -68,3 +69,13 @@ def test_resolved_manifold_bets(a_user_id: str) -> None:
# Verify that all bets convert to generic resolved bets.
for bet, market in zip(resolved_bets, markets):
manifold_to_generic_resolved_bet(bet, market)


def test_manifold_pool() -> None:
pool = ManifoldPool(NO=1, YES=2)
assert pool.size_for_outcome("NO") == 1.0
assert pool.size_for_outcome("YES") == 2.0

with pytest.raises(ValueError) as e:
pool.size_for_outcome("FOO")
assert "Unexpected outcome string" in str(e.value)
63 changes: 63 additions & 0 deletions tests/markets/test_markets.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
import pytest

from prediction_market_agent_tooling.gtypes import Probability
from prediction_market_agent_tooling.markets.agent_market import (
AgentMarket,
FilterBy,
SortBy,
)
from prediction_market_agent_tooling.markets.markets import (
MARKET_TYPE_TO_AGENT_MARKET,
MarketType,
Expand All @@ -11,3 +17,60 @@ def test_market_mapping_contains_all_types(market_type: MarketType) -> None:
assert (
market_type in MARKET_TYPE_TO_AGENT_MARKET
), f"Add {market_type} to the MARKET_TYPE_TO_AGENT_MARKET."


def test_valid_token_pool() -> None:
market = AgentMarket(
id="foo",
question="bar",
description=None,
outcomes=["yes", "no"],
outcome_token_pool={"yes": 1.1, "no": 2.0},
resolution=None,
created_time=None,
close_time=None,
current_p_yes=Probability(0.5),
url="https://example.com",
volume=None,
)
assert market.has_token_pool() is True
assert market.get_pool_tokens("yes") == 1.1
assert market.get_pool_tokens("no") == 2.0


def test_invalid_token_pool() -> None:
with pytest.raises(ValueError) as e:
AgentMarket(
id="foo",
question="bar",
description=None,
outcomes=["yes", "no"],
outcome_token_pool={"baz": 1.1, "qux": 2.0},
resolution=None,
created_time=None,
close_time=None,
current_p_yes=Probability(0.5),
url="https://example.com",
volume=None,
)
assert "do not match outcomes" in str(e.value)


@pytest.mark.parametrize("market_type", list(MarketType))
def test_get_pool_tokens(market_type: MarketType) -> None:
market_types_without_pool_tokens = [
MarketType.METACULUS,
MarketType.POLYMARKET,
]
market = market_type.market_class.get_binary_markets(
limit=1,
sort_by=SortBy.NONE,
filter_by=FilterBy.OPEN,
)[0]
if market_type in market_types_without_pool_tokens:
assert market.has_token_pool() is False
else:
assert market.has_token_pool() is True
for outcome in market.outcomes:
# Sanity check
assert market.get_pool_tokens(outcome) > 0
8 changes: 8 additions & 0 deletions tests/test_benchmark.py
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,7 @@ def test_benchmark_run(
close_time=utcnow() + timedelta(hours=48),
resolution=None,
created_time=utcnow() - timedelta(hours=48),
outcome_token_pool=None,
)
],
agents=[dummy_agent, dummy_agent_no_prediction],
Expand Down Expand Up @@ -121,6 +122,7 @@ def test_benchmarker_cache(dummy_agent: DummyAgent) -> None:
close_time=utcnow(),
resolution=Resolution.NO,
created_time=utcnow() - timedelta(hours=48),
outcome_token_pool=None,
)
]
benchmarker = bm.Benchmarker(
Expand Down Expand Up @@ -188,6 +190,7 @@ def test_benchmarker_cancelled_markets() -> None:
close_time=utcnow(),
created_time=utcnow() - timedelta(hours=48),
resolution=Resolution.CANCEL,
outcome_token_pool=None,
)
]
with pytest.raises(ValueError) as e:
Expand All @@ -214,6 +217,7 @@ def test_market_probable_resolution() -> None:
close_time=utcnow(),
created_time=utcnow() - timedelta(hours=48),
resolution=Resolution.CANCEL,
outcome_token_pool=None,
).probable_resolution
assert "Unknown resolution" in str(e)
assert (
Expand All @@ -228,6 +232,7 @@ def test_market_probable_resolution() -> None:
close_time=utcnow(),
created_time=utcnow() - timedelta(hours=48),
resolution=Resolution.YES,
outcome_token_pool=None,
).probable_resolution
== Resolution.YES
)
Expand All @@ -243,6 +248,7 @@ def test_market_probable_resolution() -> None:
close_time=utcnow(),
resolution=Resolution.NO,
created_time=utcnow() - timedelta(hours=48),
outcome_token_pool=None,
).probable_resolution
== Resolution.NO
)
Expand All @@ -258,6 +264,7 @@ def test_market_probable_resolution() -> None:
close_time=utcnow(),
resolution=Resolution.NO,
created_time=utcnow() - timedelta(hours=48),
outcome_token_pool=None,
).probable_resolution
== Resolution.NO
)
Expand All @@ -273,6 +280,7 @@ def test_market_probable_resolution() -> None:
close_time=utcnow(),
resolution=Resolution.YES,
created_time=utcnow() - timedelta(hours=48),
outcome_token_pool=None,
).probable_resolution
== Resolution.YES
)

0 comments on commit 429e1a8

Please sign in to comment.