Skip to content

Commit

Permalink
Trades return ID, matches with ID returned from AgentMarket.get_bets_…
Browse files Browse the repository at this point in the history
…made_since (#434)
  • Loading branch information
evangriffiths authored Sep 25, 2024
1 parent 9e20d4e commit 53a85c9
Show file tree
Hide file tree
Showing 13 changed files with 900 additions and 789 deletions.
1,548 changes: 792 additions & 756 deletions poetry.lock

Large diffs are not rendered by default.

15 changes: 11 additions & 4 deletions prediction_market_agent_tooling/deploy/agent.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@
SortBy,
)
from prediction_market_agent_tooling.markets.data_models import (
PlacedTrade,
Position,
ProbabilisticAnswer,
Trade,
Expand Down Expand Up @@ -110,7 +111,7 @@ class OutOfFundsError(ValueError):

class ProcessedMarket(BaseModel):
answer: ProbabilisticAnswer
trades: list[Trade]
trades: list[PlacedTrade]


class AnsweredEnum(str, Enum):
Expand Down Expand Up @@ -457,21 +458,27 @@ def process_market(
existing_position=existing_position,
)

placed_trades = []
if self.place_bet:
for trade in trades:
logger.info(f"Executing trade {trade}")

match trade.trade_type:
case TradeType.BUY:
market.buy_tokens(outcome=trade.outcome, amount=trade.amount)
id = market.buy_tokens(
outcome=trade.outcome, amount=trade.amount
)
case TradeType.SELL:
market.sell_tokens(outcome=trade.outcome, amount=trade.amount)
id = market.sell_tokens(
outcome=trade.outcome, amount=trade.amount
)
case _:
raise ValueError(f"Unexpected trade type {trade.trade_type}.")
placed_trades.append(PlacedTrade.from_trade(trade, id))

self.after_process_market(market_type, market)

processed_market = ProcessedMarket(answer=answer, trades=trades)
processed_market = ProcessedMarket(answer=answer, trades=placed_trades)
self.update_langfuse_trace_by_processed_market(market_type, processed_market)

logger.info(f"Processed market {market.question=} from {market.url=}.")
Expand Down
6 changes: 3 additions & 3 deletions prediction_market_agent_tooling/markets/agent_market.py
Original file line number Diff line number Diff line change
Expand Up @@ -166,13 +166,13 @@ def get_tiny_bet_amount(cls) -> BetAmount:
def liquidate_existing_positions(self, outcome: bool) -> None:
raise NotImplementedError("Subclasses must implement this method")

def place_bet(self, outcome: bool, amount: BetAmount) -> None:
def place_bet(self, outcome: bool, amount: BetAmount) -> str:
raise NotImplementedError("Subclasses must implement this method")

def buy_tokens(self, outcome: bool, amount: TokenAmount) -> None:
def buy_tokens(self, outcome: bool, amount: TokenAmount) -> str:
return self.place_bet(outcome=outcome, amount=amount)

def sell_tokens(self, outcome: bool, amount: TokenAmount) -> None:
def sell_tokens(self, outcome: bool, amount: TokenAmount) -> str:
raise NotImplementedError("Subclasses must implement this method")

@staticmethod
Expand Down
14 changes: 14 additions & 0 deletions prediction_market_agent_tooling/markets/data_models.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ def __str__(self) -> str:


class Bet(BaseModel):
id: str
amount: BetAmount
outcome: bool
created_time: datetime
Expand Down Expand Up @@ -126,3 +127,16 @@ class Trade(BaseModel):
trade_type: TradeType
outcome: bool
amount: TokenAmount


class PlacedTrade(Trade):
id: str

@staticmethod
def from_trade(trade: Trade, id: str) -> "PlacedTrade":
return PlacedTrade(
trade_type=trade.trade_type,
outcome=trade.outcome,
amount=trade.amount,
id=id,
)
4 changes: 3 additions & 1 deletion prediction_market_agent_tooling/markets/manifold/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,7 @@ def get_one_manifold_binary_market() -> ManifoldMarket:
)
def place_bet(
amount: Mana, market_id: str, outcome: bool, manifold_api_key: SecretStr
) -> None:
) -> ManifoldBet:
outcome_str = "YES" if outcome else "NO"
url = f"{MANIFOLD_API_BASE_URL}/v0/bet"
params = {
Expand All @@ -131,6 +131,7 @@ def place_bet(
raise RuntimeError(
f"Placing bet failed: {response.status_code} {response.reason} {response.text}"
)
return ManifoldBet.model_validate(data)
else:
raise Exception(
f"Placing bet failed: {response.status_code} {response.reason} {response.text}"
Expand Down Expand Up @@ -209,6 +210,7 @@ def manifold_to_generic_resolved_bet(

market_outcome = market.get_resolved_boolean_outcome()
return ResolvedBet(
id=bet.id,
amount=BetAmount(amount=bet.amount, currency=Currency.Mana),
outcome=bet.get_resolved_boolean_outcome(),
created_time=bet.createdTime,
Expand Down
5 changes: 3 additions & 2 deletions prediction_market_agent_tooling/markets/manifold/manifold.py
Original file line number Diff line number Diff line change
Expand Up @@ -49,15 +49,16 @@ def get_minimum_bet_to_win(self, answer: bool, amount_to_win: float) -> Mana:
# Manifold lowest bet is 1 Mana, so we need to ceil the result.
return mana_type(ceil(minimum_bet_to_win(answer, amount_to_win, self)))

def place_bet(self, outcome: bool, amount: BetAmount) -> None:
def place_bet(self, outcome: bool, amount: BetAmount) -> str:
if amount.currency != self.currency:
raise ValueError(f"Manifold bets are made in Mana. Got {amount.currency}.")
place_bet(
bet = place_bet(
amount=Mana(amount.amount),
market_id=self.id,
outcome=outcome,
manifold_api_key=APIKeys().manifold_api_key,
)
return bet.id

@staticmethod
def from_data_model(model: FullManifoldMarket) -> "ManifoldAgentMarket":
Expand Down
8 changes: 7 additions & 1 deletion prediction_market_agent_tooling/markets/omen/data_models.py
Original file line number Diff line number Diff line change
Expand Up @@ -382,7 +382,7 @@ class OmenBetCreator(BaseModel):


class OmenBet(BaseModel):
id: HexAddress
id: HexAddress # A concatenation of: FPMM contract ID, trader ID and nonce. See https://github.com/protofire/omen-subgraph/blob/f92bbfb6fa31ed9cd5985c416a26a2f640837d8b/src/FixedProductMarketMakerMapping.ts#L109
title: str
collateralToken: HexAddress
outcomeTokenMarginalPrice: xDai
Expand Down Expand Up @@ -431,6 +431,9 @@ def get_profit(self) -> ProfitAmount:

def to_bet(self) -> Bet:
return Bet(
id=str(
self.transactionHash
), # Use the transaction hash instead of the bet id - both are valid, but we return the transaction hash from the trade functions, so be consistent here.
amount=BetAmount(amount=self.collateralAmountUSD, currency=Currency.xDai),
outcome=self.boolean_outcome,
created_time=self.creation_datetime,
Expand All @@ -445,6 +448,9 @@ def to_generic_resolved_bet(self) -> ResolvedBet:
)

return ResolvedBet(
id=str(
self.transactionHash
), # Use the transaction hash instead of the bet id - both are valid, but we return the transaction hash from the trade functions, so be consistent here.
amount=BetAmount(amount=self.collateralAmountUSD, currency=Currency.xDai),
outcome=self.boolean_outcome,
created_time=self.creation_datetime,
Expand Down
30 changes: 17 additions & 13 deletions prediction_market_agent_tooling/markets/omen/omen.py
Original file line number Diff line number Diff line change
Expand Up @@ -189,15 +189,15 @@ def place_bet(
omen_auto_deposit: bool = True,
web3: Web3 | None = None,
api_keys: APIKeys | None = None,
) -> None:
) -> str:
if not self.can_be_traded():
raise ValueError(
f"Market {self.id} is not open for trading. Cannot place bet."
)
if amount.currency != self.currency:
raise ValueError(f"Omen bets are made in xDai. Got {amount.currency}.")
amount_xdai = xDai(amount.amount)
binary_omen_buy_outcome_tx(
return binary_omen_buy_outcome_tx(
api_keys=api_keys if api_keys is not None else APIKeys(),
amount=amount_xdai,
market=self,
Expand All @@ -212,7 +212,7 @@ def buy_tokens(
amount: TokenAmount,
web3: Web3 | None = None,
api_keys: APIKeys | None = None,
) -> None:
) -> str:
return self.place_bet(
outcome=outcome,
amount=amount,
Expand Down Expand Up @@ -245,7 +245,7 @@ def sell_tokens(
auto_withdraw: bool = False,
api_keys: APIKeys | None = None,
web3: Web3 | None = None,
) -> None:
) -> str:
if not self.can_be_traded():
raise ValueError(
f"Market {self.id} is not open for trading. Cannot sell tokens."
Expand All @@ -258,7 +258,7 @@ def sell_tokens(
outcome=outcome,
web3=web3,
)
binary_omen_sell_outcome_tx(
return binary_omen_sell_outcome_tx(
amount=collateral,
api_keys=api_keys if api_keys is not None else APIKeys(),
market=self,
Expand Down Expand Up @@ -667,7 +667,7 @@ def omen_buy_outcome_tx(
outcome: str,
auto_deposit: bool,
web3: Web3 | None = None,
) -> None:
) -> str:
"""
Bets the given amount of xDai for the given outcome in the given market.
"""
Expand Down Expand Up @@ -703,14 +703,16 @@ def omen_buy_outcome_tx(
)

# Buy shares using the deposited xDai in the collateral token.
market_contract.buy(
tx_receipt = market_contract.buy(
api_keys=api_keys,
amount_wei=amount_wei_to_buy,
outcome_index=outcome_index,
min_outcome_tokens_to_buy=expected_shares,
web3=web3,
)

return tx_receipt["transactionHash"].hex()


def binary_omen_buy_outcome_tx(
api_keys: APIKeys,
Expand All @@ -719,8 +721,8 @@ def binary_omen_buy_outcome_tx(
binary_outcome: bool,
auto_deposit: bool,
web3: Web3 | None = None,
) -> None:
omen_buy_outcome_tx(
) -> str:
return omen_buy_outcome_tx(
api_keys=api_keys,
amount=amount,
market=market,
Expand All @@ -737,7 +739,7 @@ def omen_sell_outcome_tx(
outcome: str,
auto_withdraw: bool,
web3: Web3 | None = None,
) -> None:
) -> str:
"""
Sells the given xDai value of shares corresponding to the given outcome in
the given market.
Expand Down Expand Up @@ -778,7 +780,7 @@ def omen_sell_outcome_tx(
web3=web3,
)
# Sell the shares.
market_contract.sell(
tx_receipt = market_contract.sell(
api_keys,
amount_wei,
outcome_index,
Expand All @@ -800,6 +802,8 @@ def omen_sell_outcome_tx(
web3,
)

return tx_receipt["transactionHash"].hex()


def binary_omen_sell_outcome_tx(
api_keys: APIKeys,
Expand All @@ -808,8 +812,8 @@ def binary_omen_sell_outcome_tx(
binary_outcome: bool,
auto_withdraw: bool,
web3: Web3 | None = None,
) -> None:
omen_sell_outcome_tx(
) -> str:
return omen_sell_outcome_tx(
api_keys=api_keys,
amount=amount,
market=market,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ def from_data_model(model: PolymarketMarketWithPrices) -> "PolymarketAgentMarket
def get_tiny_bet_amount(cls) -> BetAmount:
raise NotImplementedError("TODO: Implement to allow betting on Polymarket.")

def place_bet(self, outcome: bool, amount: BetAmount) -> None:
def place_bet(self, outcome: bool, amount: BetAmount) -> str:
raise NotImplementedError("TODO: Implement to allow betting on Polymarket.")

@staticmethod
Expand Down
10 changes: 5 additions & 5 deletions prediction_market_agent_tooling/tools/langfuse_client_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,9 @@
from pydantic import BaseModel

from prediction_market_agent_tooling.markets.data_models import (
PlacedTrade,
ProbabilisticAnswer,
ResolvedBet,
Trade,
TradeType,
)
from prediction_market_agent_tooling.markets.omen.omen import OmenAgentMarket
Expand All @@ -20,10 +20,10 @@ class ProcessMarketTrace(BaseModel):
timestamp: datetime
market: OmenAgentMarket
answer: ProbabilisticAnswer
trades: list[Trade]
trades: list[PlacedTrade]

@property
def buy_trade(self) -> Trade:
def buy_trade(self) -> PlacedTrade:
buy_trades = [t for t in self.trades if t.trade_type == TradeType.BUY]
if len(buy_trades) == 1:
return buy_trades[0]
Expand Down Expand Up @@ -107,10 +107,10 @@ def trace_to_answer(trace: TraceWithDetails) -> ProbabilisticAnswer:
return ProbabilisticAnswer.model_validate(trace.output["answer"])


def trace_to_trades(trace: TraceWithDetails) -> list[Trade]:
def trace_to_trades(trace: TraceWithDetails) -> list[PlacedTrade]:
assert trace.output is not None, "Trace output is None"
assert trace.output["trades"] is not None, "Trace output trades is None"
return [Trade.model_validate(t) for t in trace.output["trades"]]
return [PlacedTrade.model_validate(t) for t in trace.output["trades"]]


def get_closest_datetime_from_list(
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.18"
version = "0.49.0"
description = "Tools to benchmark, deploy and monitor prediction market agents."
authors = ["Gnosis"]
readme = "README.md"
Expand Down
34 changes: 34 additions & 0 deletions tests_integration/markets/omen/test_get_bets.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import time

import pytest

from prediction_market_agent_tooling.config import APIKeys
from prediction_market_agent_tooling.markets.agent_market import FilterBy, SortBy
from prediction_market_agent_tooling.markets.omen.omen import OmenAgentMarket
from prediction_market_agent_tooling.tools.utils import utcnow
from tests.utils import RUN_PAID_TESTS


@pytest.mark.skipif(not RUN_PAID_TESTS, reason="This test costs money to run.")
def test_match_bet_ids_with_get_subgraph_bets() -> None:
market = OmenAgentMarket.get_binary_markets(
limit=1,
sort_by=SortBy.CLOSING_SOONEST,
filter_by=FilterBy.OPEN,
)[0]
now = utcnow()
id0 = market.buy_tokens(outcome=True, amount=market.get_tiny_bet_amount())
id1 = market.buy_tokens(outcome=True, amount=market.get_tiny_bet_amount())
assert id0 != id1

time.sleep(10) # wait for the subgraph to index the bets
keys = APIKeys()
bets = OmenAgentMarket.get_bets_made_since(
better_address=keys.bet_from_address,
start_time=now,
)
# Ensure bets are sorted by ascending time
bets = sorted(bets, key=lambda bet: bet.created_time)
assert len(bets) == 2
assert bets[0].id == id0
assert bets[1].id == id1
Loading

0 comments on commit 53a85c9

Please sign in to comment.