Skip to content

Commit

Permalink
Add helper script for unfinding a market (#503)
Browse files Browse the repository at this point in the history
  • Loading branch information
kongzii committed Oct 17, 2024
1 parent 18c381f commit 9ef5060
Show file tree
Hide file tree
Showing 23 changed files with 251 additions and 108 deletions.
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -162,3 +162,5 @@ cython_debug/

tests_files/*
!tests_files/.gitkeep

bet_strategy_benchmark*
15 changes: 12 additions & 3 deletions examples/monitor/match_bets_with_langfuse_traces.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
from pathlib import Path
from typing import Any

import pandas as pd
Expand Down Expand Up @@ -89,6 +90,9 @@ def get_outcome_for_trace(


if __name__ == "__main__":
output_directory = Path("bet_strategy_benchmark")
output_directory.mkdir(parents=True, exist_ok=True)

# Get the private keys for the agents from GCP Secret Manager
agent_gcp_secret_map = {
"DeployablePredictionProphetGPT4TurboFinalAgent": "pma-prophetgpt4turbo-final",
Expand Down Expand Up @@ -240,7 +244,8 @@ def get_outcome_for_trace(

details.sort(key=lambda x: x["sim_profit"], reverse=True)
pd.DataFrame.from_records(details).to_csv(
f"{agent_name} - {strategy} - all bets.csv", index=False
output_directory / f"{agent_name} - {strategy} - all bets.csv",
index=False,
)

sum_squared_errors = 0.0
Expand Down Expand Up @@ -297,7 +302,9 @@ def get_outcome_for_trace(
+ simulations_df.to_markdown(index=False)
)
# export details per agent
pd.DataFrame.from_records(details).to_csv(f"{agent_name}_details.csv")
pd.DataFrame.from_records(details).to_csv(
output_directory / f"{agent_name}_details.csv"
)

print(f"Correlation between p_yes mse and total profit:")
for strategy_name, mse_profit in strat_mse_profits.items():
Expand All @@ -306,5 +313,7 @@ def get_outcome_for_trace(
correlation = pd.Series(mse).corr(pd.Series(profit))
print(f"{strategy_name}: {correlation=}")

with open("match_bets_with_langfuse_traces_overall.md", "w") as overall_f:
with open(
output_directory / "match_bets_with_langfuse_traces_overall.md", "w"
) as overall_f:
overall_f.write(overall_md)
77 changes: 32 additions & 45 deletions prediction_market_agent_tooling/deploy/betting_strategy.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

from prediction_market_agent_tooling.gtypes import xDai
from prediction_market_agent_tooling.loggers import logger
from prediction_market_agent_tooling.markets.agent_market import AgentMarket
from prediction_market_agent_tooling.markets.agent_market import AgentMarket, MarketFees
from prediction_market_agent_tooling.markets.data_models import (
Currency,
Position,
Expand All @@ -19,7 +19,6 @@
)
from prediction_market_agent_tooling.tools.betting_strategies.kelly_criterion import (
get_kelly_bet_full,
get_kelly_bet_simplified,
)
from prediction_market_agent_tooling.tools.betting_strategies.utils import SimpleBet
from prediction_market_agent_tooling.tools.utils import check_not_none
Expand Down Expand Up @@ -153,32 +152,24 @@ def calculate_trades(
market: AgentMarket,
) -> list[Trade]:
outcome_token_pool = check_not_none(market.outcome_token_pool)
kelly_bet = (
get_kelly_bet_full(
yes_outcome_pool_size=outcome_token_pool[
market.get_outcome_str_from_bool(True)
],
no_outcome_pool_size=outcome_token_pool[
market.get_outcome_str_from_bool(False)
],
estimated_p_yes=answer.p_yes,
max_bet=self.max_bet_amount,
confidence=answer.confidence,
)
if market.has_token_pool()
else get_kelly_bet_simplified(
self.max_bet_amount,
market.current_p_yes,
answer.p_yes,
answer.confidence,
)
kelly_bet = get_kelly_bet_full(
yes_outcome_pool_size=outcome_token_pool[
market.get_outcome_str_from_bool(True)
],
no_outcome_pool_size=outcome_token_pool[
market.get_outcome_str_from_bool(False)
],
estimated_p_yes=answer.p_yes,
max_bet=self.max_bet_amount,
confidence=answer.confidence,
fees=market.fees,
)

kelly_bet_size = kelly_bet.size
if self.max_price_impact:
# Adjust amount
max_price_impact_bet_amount = self.calculate_bet_amount_for_price_impact(
market, kelly_bet, 0
market, kelly_bet
)

# We just don't want Kelly size to extrapolate price_impact - hence we take the min.
Expand All @@ -196,15 +187,20 @@ def calculate_trades(
return trades

def calculate_price_impact_for_bet_amount(
self, buy_direction: bool, bet_amount: float, yes: float, no: float, fee: float
self,
buy_direction: bool,
bet_amount: float,
yes: float,
no: float,
fees: MarketFees,
) -> float:
total_outcome_tokens = yes + no
expected_price = (
no / total_outcome_tokens if buy_direction else yes / total_outcome_tokens
)

tokens_to_buy = get_buy_outcome_token_amount(
bet_amount, buy_direction, yes, no, fee
bet_amount, buy_direction, yes, no, fees
)

actual_price = bet_amount / tokens_to_buy
Expand All @@ -216,7 +212,6 @@ def calculate_bet_amount_for_price_impact(
self,
market: AgentMarket,
kelly_bet: SimpleBet,
fee: float,
) -> float:
def calculate_price_impact_deviation_from_target_price_impact(
bet_amount: xDai,
Expand All @@ -226,7 +221,7 @@ def calculate_price_impact_deviation_from_target_price_impact(
bet_amount,
yes_outcome_pool_size,
no_outcome_pool_size,
fee,
market.fees,
)
# We return abs for the algorithm to converge to 0 instead of the min (and possibly negative) value.

Expand Down Expand Up @@ -285,25 +280,17 @@ def calculate_trades(
estimated_p_yes = float(answer.p_yes > 0.5)
confidence = 1.0

kelly_bet = (
get_kelly_bet_full(
yes_outcome_pool_size=outcome_token_pool[
market.get_outcome_str_from_bool(True)
],
no_outcome_pool_size=outcome_token_pool[
market.get_outcome_str_from_bool(False)
],
estimated_p_yes=estimated_p_yes,
max_bet=adjusted_bet_amount,
confidence=confidence,
)
if market.has_token_pool()
else get_kelly_bet_simplified(
adjusted_bet_amount,
market.current_p_yes,
estimated_p_yes,
confidence,
)
kelly_bet = get_kelly_bet_full(
yes_outcome_pool_size=outcome_token_pool[
market.get_outcome_str_from_bool(True)
],
no_outcome_pool_size=outcome_token_pool[
market.get_outcome_str_from_bool(False)
],
estimated_p_yes=estimated_p_yes,
max_bet=adjusted_bet_amount,
confidence=confidence,
fees=market.fees,
)

amounts = {
Expand Down
2 changes: 1 addition & 1 deletion prediction_market_agent_tooling/jobs/omen/omen_jobs.py
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ def from_omen_agent_market(market: OmenAgentMarket) -> "OmenJobAgentMarket":
market_maker_contract_address_checksummed=market.market_maker_contract_address_checksummed,
condition=market.condition,
finalized_time=market.finalized_time,
fee=market.fee,
fees=market.fees,
)


Expand Down
12 changes: 11 additions & 1 deletion prediction_market_agent_tooling/markets/agent_market.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
from enum import Enum

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

from prediction_market_agent_tooling.config import APIKeys
Expand All @@ -16,6 +16,7 @@
ResolvedBet,
TokenAmount,
)
from prediction_market_agent_tooling.markets.market_fees import MarketFees
from prediction_market_agent_tooling.tools.utils import (
DatetimeUTC,
check_not_none,
Expand Down Expand Up @@ -60,6 +61,7 @@ class AgentMarket(BaseModel):
current_p_yes: Probability
url: str
volume: float | None # Should be in currency of `currency` above.
fees: MarketFees

@field_validator("outcome_token_pool")
def validate_outcome_token_pool(
Expand All @@ -77,6 +79,14 @@ def validate_outcome_token_pool(
)
return outcome_token_pool

@model_validator(mode="before")
def handle_legacy_fee(cls, data: dict[str, t.Any]) -> dict[str, t.Any]:
# Backward compatibility for older `AgentMarket` without `fees`.
if "fees" not in data and "fee" in data:
data["fees"] = MarketFees(absolute=0.0, bet_proportion=data["fee"])
del data["fee"]
return data

@property
def current_p_no(self) -> Probability:
return Probability(1 - self.current_p_yes)
Expand Down
8 changes: 8 additions & 0 deletions prediction_market_agent_tooling/markets/manifold/manifold.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
from prediction_market_agent_tooling.markets.agent_market import (
AgentMarket,
FilterBy,
MarketFees,
SortBy,
)
from prediction_market_agent_tooling.markets.data_models import BetAmount, Currency
Expand Down Expand Up @@ -33,6 +34,13 @@ class ManifoldAgentMarket(AgentMarket):
currency: t.ClassVar[Currency] = Currency.Mana
base_url: t.ClassVar[str] = MANIFOLD_BASE_URL

# Manifold has additional fees than `platform_absolute`, but they don't expose them in the API before placing the bet, see https://docs.manifold.markets/api.
# So we just consider them as 0, which anyway is true for all markets I randomly checked on Manifold.
fees: MarketFees = MarketFees(
bet_proportion=0,
absolute=0.25, # For doing trades via API.
)

def get_last_trade_p_yes(self) -> Probability:
"""On Manifold, probablities aren't updated after the closure, so we can just use the current probability"""
return self.current_p_yes
Expand Down
36 changes: 36 additions & 0 deletions prediction_market_agent_tooling/markets/market_fees.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
from pydantic import BaseModel, Field


class MarketFees(BaseModel):
bet_proportion: float = Field(
..., ge=0.0, lt=1.0
) # proportion of the bet, from 0 to 1
absolute: float # absolute value paid in the currency of the market

@staticmethod
def get_zero_fees(
bet_proportion: float = 0.0,
absolute: float = 0.0,
) -> "MarketFees":
return MarketFees(
bet_proportion=bet_proportion,
absolute=absolute,
)

def total_fee_absolute_value(self, bet_amount: float) -> float:
"""
Returns the total fee in absolute terms, including both proportional and fixed fees.
"""
return self.bet_proportion * bet_amount + self.absolute

def total_fee_relative_value(self, bet_amount: float) -> float:
"""
Returns the total fee relative to the bet amount, including both proportional and fixed fees.
"""
if bet_amount == 0:
return 0.0
total_fee = self.total_fee_absolute_value(bet_amount)
return total_fee / bet_amount

def get_bet_size_after_fees(self, bet_amount: float) -> float:
return bet_amount * (1 - self.bet_proportion) - self.absolute
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
from prediction_market_agent_tooling.markets.agent_market import (
AgentMarket,
FilterBy,
MarketFees,
SortBy,
)
from prediction_market_agent_tooling.markets.metaculus.api import (
Expand All @@ -29,6 +30,7 @@ class MetaculusAgentMarket(AgentMarket):
description: str | None = (
None # Metaculus markets don't have a description, so just default to None.
)
fees: MarketFees = MarketFees.get_zero_fees() # No fees on Metaculus.

@staticmethod
def from_data_model(model: MetaculusQuestion) -> "MetaculusAgentMarket":
Expand Down
Loading

0 comments on commit 9ef5060

Please sign in to comment.