Skip to content
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

Trades return ID, matches with ID returned from AgentMarket.get_bets_made_since #434

Merged
merged 8 commits into from
Sep 25, 2024
Merged
Show file tree
Hide file tree
Changes from 7 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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]
gabrielfior marked this conversation as resolved.
Show resolved Hide resolved


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
)
evangriffiths marked this conversation as resolved.
Show resolved Hide resolved
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
evangriffiths marked this conversation as resolved.
Show resolved Hide resolved
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
gabrielfior marked this conversation as resolved.
Show resolved Hide resolved

@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,
evangriffiths marked this conversation as resolved.
Show resolved Hide resolved
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()
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If I try to access the property like tx_receipt.transactionHash mypy complains, so accessing it as a dict key

Copy link
Contributor Author

@evangriffiths evangriffiths Sep 25, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could do

    trader_address = api_keys.bet_from_address
    nonce = ContractOnGnosisChain.get_transaction_count(trader_address)
    return str(tx_receipt.to) + str(trader_address.lower()) + str(nonce)

instead (to match OmenBet.id instead of OmenBet.transactionHash), but chose this for simplicity



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.")
evangriffiths marked this conversation as resolved.
Show resolved Hide resolved

@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.")
gabrielfior marked this conversation as resolved.
Show resolved Hide resolved
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
evangriffiths marked this conversation as resolved.
Show resolved Hide resolved
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
Loading