Skip to content

Commit

Permalink
Getting jobs (#409)
Browse files Browse the repository at this point in the history
  • Loading branch information
kongzii authored Sep 19, 2024
1 parent 7f0770c commit 45601b6
Show file tree
Hide file tree
Showing 12 changed files with 242 additions and 23 deletions.
2 changes: 1 addition & 1 deletion prediction_market_agent_tooling/gtypes.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@
xDai = NewType("xDai", float)
GNO = NewType("GNO", float)
ABI = NewType("ABI", str)
OmenOutcomeToken = NewType("OmenOutcomeToken", int)
OmenOutcomeToken = NewType("OmenOutcomeToken", Wei)
OutcomeStr = NewType("OutcomeStr", str)
Probability = NewType("Probability", float)
Mana = NewType("Mana", float) # Manifold's "currency"
Expand Down
Empty file.
45 changes: 45 additions & 0 deletions prediction_market_agent_tooling/jobs/jobs.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import typing as t

from prediction_market_agent_tooling.jobs.jobs_models import JobAgentMarket
from prediction_market_agent_tooling.jobs.omen.omen_jobs import OmenJobAgentMarket
from prediction_market_agent_tooling.markets.agent_market import FilterBy, SortBy
from prediction_market_agent_tooling.markets.markets import MarketType

JOB_MARKET_TYPE_TO_JOB_AGENT_MARKET: dict[MarketType, type[JobAgentMarket]] = {
MarketType.OMEN: OmenJobAgentMarket,
}


@t.overload
def get_jobs(
market_type: t.Literal[MarketType.OMEN],
limit: int | None,
filter_by: FilterBy = FilterBy.OPEN,
sort_by: SortBy = SortBy.NONE,
) -> t.Sequence[OmenJobAgentMarket]:
...


@t.overload
def get_jobs(
market_type: MarketType,
limit: int | None,
filter_by: FilterBy = FilterBy.OPEN,
sort_by: SortBy = SortBy.NONE,
) -> t.Sequence[JobAgentMarket]:
...


def get_jobs(
market_type: MarketType,
limit: int | None,
filter_by: FilterBy = FilterBy.OPEN,
sort_by: SortBy = SortBy.NONE,
) -> t.Sequence[JobAgentMarket]:
job_class = JOB_MARKET_TYPE_TO_JOB_AGENT_MARKET[market_type]
markets = job_class.get_jobs(
limit=limit,
sort_by=sort_by,
filter_by=filter_by,
)
return markets
53 changes: 53 additions & 0 deletions prediction_market_agent_tooling/jobs/jobs_models.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import typing as t
from abc import ABC, abstractmethod
from datetime import datetime

from pydantic import BaseModel

from prediction_market_agent_tooling.markets.agent_market import AgentMarket
from prediction_market_agent_tooling.markets.omen.omen_subgraph_handler import (
FilterBy,
SortBy,
)


class SimpleJob(BaseModel):
id: str
job: str
reward: float
currency: str
deadline: datetime


class JobAgentMarket(AgentMarket, ABC):
CATEGORY: t.ClassVar[str]

@property
@abstractmethod
def job(self) -> str:
"""Holds description of the job that needs to be done."""

@property
@abstractmethod
def deadline(self) -> datetime:
"""Deadline for the job completion."""

@abstractmethod
def get_reward(self, max_bond: float) -> float:
"""Reward for completing this job."""

@abstractmethod
@classmethod
def get_jobs(
cls, limit: int | None, filter_by: FilterBy, sort_by: SortBy
) -> t.Sequence["JobAgentMarket"]:
"""Get all available jobs."""

def to_simple_job(self, max_bond: float) -> SimpleJob:
return SimpleJob(
id=self.id,
job=self.job,
reward=self.get_reward(max_bond),
currency=self.currency.value,
deadline=self.deadline,
)
113 changes: 113 additions & 0 deletions prediction_market_agent_tooling/jobs/omen/omen_jobs.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
import typing as t
from datetime import datetime

from web3 import Web3

from prediction_market_agent_tooling.deploy.betting_strategy import (
Currency,
KellyBettingStrategy,
ProbabilisticAnswer,
TradeType,
)
from prediction_market_agent_tooling.gtypes import Probability
from prediction_market_agent_tooling.jobs.jobs_models import JobAgentMarket
from prediction_market_agent_tooling.markets.omen.omen import (
BetAmount,
OmenAgentMarket,
OmenMarket,
)
from prediction_market_agent_tooling.markets.omen.omen_subgraph_handler import (
FilterBy,
OmenSubgraphHandler,
SortBy,
)


class OmenJobAgentMarket(OmenAgentMarket, JobAgentMarket):
CATEGORY = "jobs"

@property
def job(self) -> str:
"""Omen market's have only question, so that's where the job description is."""
return self.question

@property
def deadline(self) -> datetime:
return self.close_time

def get_reward(self, max_bond: float) -> float:
return compute_job_reward(self, max_bond)

@classmethod
def get_jobs(
cls, limit: int | None, filter_by: FilterBy, sort_by: SortBy
) -> t.Sequence["OmenJobAgentMarket"]:
markets = OmenSubgraphHandler().get_omen_binary_markets_simple(
limit=limit,
filter_by=filter_by,
sort_by=sort_by,
category=cls.CATEGORY,
)
return [OmenJobAgentMarket.from_omen_market(market) for market in markets]

@staticmethod
def from_omen_market(market: OmenMarket) -> "OmenJobAgentMarket":
return OmenJobAgentMarket.from_omen_agent_market(
OmenAgentMarket.from_data_model(market)
)

@staticmethod
def from_omen_agent_market(market: OmenAgentMarket) -> "OmenJobAgentMarket":
return OmenJobAgentMarket(
id=market.id,
question=market.question,
description=market.description,
outcomes=market.outcomes,
outcome_token_pool=market.outcome_token_pool,
resolution=market.resolution,
created_time=market.created_time,
close_time=market.close_time,
current_p_yes=market.current_p_yes,
url=market.url,
volume=market.volume,
creator=market.creator,
collateral_token_contract_address_checksummed=market.collateral_token_contract_address_checksummed,
market_maker_contract_address_checksummed=market.market_maker_contract_address_checksummed,
condition=market.condition,
finalized_time=market.finalized_time,
fee=market.fee,
)


def compute_job_reward(
market: OmenAgentMarket, max_bond: float, web3: Web3 | None = None
) -> float:
# 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.
required_trades = KellyBettingStrategy(max_bet_amount=max_bond).calculate_trades(
existing_position=None,
# We assume that we finish the job and so the probability of the market happening will be 100%.
answer=ProbabilisticAnswer(p_yes=Probability(1.0), confidence=1.0),
market=market,
)

assert (
len(required_trades) == 1
), f"Shouldn't process same job twice: {required_trades}"
trade = required_trades[0]
assert trade.trade_type == TradeType.BUY, "Should only buy on job markets."
assert trade.outcome, "Should buy only YES on job markets."
assert (
trade.amount.currency == Currency.xDai
), "Should work only on real-money markets."

reward = (
market.get_buy_token_amount(
bet_amount=BetAmount(
amount=trade.amount.amount, currency=trade.amount.currency
),
direction=trade.outcome,
).amount
- trade.amount.amount
)

return reward
3 changes: 3 additions & 0 deletions prediction_market_agent_tooling/markets/omen/data_models.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
ChecksumAddress,
HexAddress,
HexBytes,
HexStr,
OmenOutcomeToken,
Probability,
Wei,
Expand All @@ -30,8 +31,10 @@

OMEN_TRUE_OUTCOME = "Yes"
OMEN_FALSE_OUTCOME = "No"
OMEN_BINARY_MARKET_OUTCOMES = [OMEN_TRUE_OUTCOME, OMEN_FALSE_OUTCOME]
INVALID_ANSWER = 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF
INVALID_ANSWER_HEX_BYTES = HexBytes(INVALID_ANSWER)
INVALID_ANSWER_STR = HexStr(INVALID_ANSWER_HEX_BYTES.hex())
OMEN_BASE_URL = "https://aiomen.eth.limo"
PRESAGIO_BASE_URL = "https://presagio.pages.dev"

Expand Down
4 changes: 0 additions & 4 deletions prediction_market_agent_tooling/markets/omen/omen.py
Original file line number Diff line number Diff line change
Expand Up @@ -97,10 +97,6 @@ class OmenAgentMarket(AgentMarket):
close_time: datetime
fee: float # proportion, from 0 to 1

INVALID_MARKET_ANSWER: HexStr = HexStr(
"0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF"
)

_binary_market_p_yes_history: list[Probability] | None = None
description: str | None = (
None # Omen markets don't have a description, so just default to None.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,7 @@
from prediction_market_agent_tooling.loggers import logger
from prediction_market_agent_tooling.markets.agent_market import FilterBy, SortBy
from prediction_market_agent_tooling.markets.omen.data_models import (
OMEN_FALSE_OUTCOME,
OMEN_TRUE_OUTCOME,
OMEN_BINARY_MARKET_OUTCOMES,
OmenBet,
OmenMarket,
OmenPosition,
Expand Down Expand Up @@ -204,7 +203,7 @@ def _build_where_statements(
self,
creator: t.Optional[HexAddress] = None,
creator_in: t.Optional[t.Sequence[HexAddress]] = None,
outcomes: list[str] = [OMEN_TRUE_OUTCOME, OMEN_FALSE_OUTCOME],
outcomes: list[str] = OMEN_BINARY_MARKET_OUTCOMES,
created_after: t.Optional[datetime] = None,
opened_before: t.Optional[datetime] = None,
opened_after: t.Optional[datetime] = None,
Expand All @@ -217,6 +216,7 @@ def _build_where_statements(
id_in: list[str] | None = None,
excluded_questions: set[str] | None = None,
collateral_token_address_in: tuple[ChecksumAddress, ...] | None = None,
category: str | None = None,
) -> dict[str, t.Any]:
where_stms: dict[str, t.Any] = {
"isPendingArbitration": False,
Expand Down Expand Up @@ -282,6 +282,9 @@ def _build_where_statements(
finalized_after
)

if category:
where_stms["category"] = category

# `excluded_question_titles` can not be an empty list, otherwise the API bugs out and returns nothing.
excluded_question_titles = [""]
if excluded_questions:
Expand Down Expand Up @@ -324,8 +327,10 @@ def get_omen_binary_markets_simple(
# Additional filters, these can not be modified by the enums above.
created_after: datetime | None = None,
excluded_questions: set[str] | None = None, # question titles
collateral_token_address_in: tuple[ChecksumAddress, ...]
| None = SAFE_COLLATERAL_TOKEN_MARKETS,
collateral_token_address_in: (
tuple[ChecksumAddress, ...] | None
) = SAFE_COLLATERAL_TOKEN_MARKETS,
category: str | None = None,
) -> t.List[OmenMarket]:
"""
Simplified `get_omen_binary_markets` method, which allows to fetch markets based on the filter_by and sort_by values.
Expand Down Expand Up @@ -363,6 +368,7 @@ def get_omen_binary_markets_simple(
created_after=created_after,
excluded_questions=excluded_questions,
collateral_token_address_in=collateral_token_address_in,
category=category,
)

def get_omen_binary_markets(
Expand All @@ -383,9 +389,11 @@ def get_omen_binary_markets(
excluded_questions: set[str] | None = None, # question titles
sort_by_field: FieldPath | None = None,
sort_direction: str | None = None,
outcomes: list[str] = [OMEN_TRUE_OUTCOME, OMEN_FALSE_OUTCOME],
collateral_token_address_in: tuple[ChecksumAddress, ...]
| None = SAFE_COLLATERAL_TOKEN_MARKETS,
outcomes: list[str] = OMEN_BINARY_MARKET_OUTCOMES,
collateral_token_address_in: (
tuple[ChecksumAddress, ...] | None
) = SAFE_COLLATERAL_TOKEN_MARKETS,
category: str | None = None,
) -> t.List[OmenMarket]:
"""
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.
Expand All @@ -406,6 +414,7 @@ def get_omen_binary_markets(
excluded_questions=excluded_questions,
liquidity_bigger_than=liquidity_bigger_than,
collateral_token_address_in=collateral_token_address_in,
category=category,
)

# These values can not be set to `None`, but they can be omitted.
Expand Down
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[tool.poetry]
name = "prediction-market-agent-tooling"
version = "0.48.14"
version = "0.48.15"
description = "Tools to benchmark, deploy and monitor prediction market agents."
authors = ["Gnosis"]
readme = "README.md"
Expand Down
5 changes: 2 additions & 3 deletions scripts/create_market_omen.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,7 @@
from prediction_market_agent_tooling.gtypes import private_key_type, xdai_type
from prediction_market_agent_tooling.loggers import logger
from prediction_market_agent_tooling.markets.omen.data_models import (
OMEN_FALSE_OUTCOME,
OMEN_TRUE_OUTCOME,
OMEN_BINARY_MARKET_OUTCOMES,
)
from prediction_market_agent_tooling.markets.omen.omen import omen_create_market_tx
from prediction_market_agent_tooling.markets.omen.omen_contracts import (
Expand All @@ -28,7 +27,7 @@ def main(
cl_token: CollateralTokenChoice = CollateralTokenChoice.wxdai,
fee: float = typer.Option(OMEN_DEFAULT_MARKET_FEE),
language: str = typer.Option("en"),
outcomes: list[str] = typer.Option([OMEN_TRUE_OUTCOME, OMEN_FALSE_OUTCOME]),
outcomes: list[str] = typer.Option(OMEN_BINARY_MARKET_OUTCOMES),
auto_deposit: bool = typer.Option(False),
) -> None:
"""
Expand Down
6 changes: 3 additions & 3 deletions tests/markets/test_betting_strategies.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,10 @@
from prediction_market_agent_tooling.gtypes import (
HexBytes,
Mana,
OmenOutcomeToken,
Probability,
Wei,
mana_type,
omen_outcome_type,
usd_type,
wei_type,
xdai_type,
Expand Down Expand Up @@ -64,8 +64,8 @@ def omen_market() -> OmenMarket:
),
outcomes=["Yes", "No"],
outcomeTokenAmounts=[
OmenOutcomeToken(7277347438897016099),
OmenOutcomeToken(13741270543921756242),
omen_outcome_type(7277347438897016099),
omen_outcome_type(13741270543921756242),
],
outcomeTokenMarginalPrices=[
xdai_type("0.6537666061181695741160552853310822"),
Expand Down
Loading

0 comments on commit 45601b6

Please sign in to comment.