prediction-market-agent-tooling 0.48.10__tar.gz → 0.48.12__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {prediction_market_agent_tooling-0.48.10 → prediction_market_agent_tooling-0.48.12}/PKG-INFO +1 -1
- {prediction_market_agent_tooling-0.48.10 → prediction_market_agent_tooling-0.48.12}/prediction_market_agent_tooling/deploy/agent.py +35 -33
- prediction_market_agent_tooling-0.48.12/prediction_market_agent_tooling/deploy/betting_strategy.py +187 -0
- {prediction_market_agent_tooling-0.48.10 → prediction_market_agent_tooling-0.48.12}/prediction_market_agent_tooling/markets/agent_market.py +45 -1
- {prediction_market_agent_tooling-0.48.10 → prediction_market_agent_tooling-0.48.12}/prediction_market_agent_tooling/markets/data_models.py +11 -0
- {prediction_market_agent_tooling-0.48.10 → prediction_market_agent_tooling-0.48.12}/prediction_market_agent_tooling/markets/manifold/data_models.py +6 -0
- {prediction_market_agent_tooling-0.48.10 → prediction_market_agent_tooling-0.48.12}/prediction_market_agent_tooling/markets/manifold/manifold.py +3 -0
- {prediction_market_agent_tooling-0.48.10 → prediction_market_agent_tooling-0.48.12}/prediction_market_agent_tooling/markets/metaculus/metaculus.py +1 -0
- {prediction_market_agent_tooling-0.48.10 → prediction_market_agent_tooling-0.48.12}/prediction_market_agent_tooling/markets/omen/data_models.py +4 -0
- {prediction_market_agent_tooling-0.48.10 → prediction_market_agent_tooling-0.48.12}/prediction_market_agent_tooling/markets/omen/omen.py +69 -6
- {prediction_market_agent_tooling-0.48.10 → prediction_market_agent_tooling-0.48.12}/prediction_market_agent_tooling/markets/omen/omen_subgraph_handler.py +18 -8
- {prediction_market_agent_tooling-0.48.10 → prediction_market_agent_tooling-0.48.12}/prediction_market_agent_tooling/markets/polymarket/polymarket.py +1 -0
- {prediction_market_agent_tooling-0.48.10 → prediction_market_agent_tooling-0.48.12}/prediction_market_agent_tooling/tools/contract.py +6 -1
- {prediction_market_agent_tooling-0.48.10 → prediction_market_agent_tooling-0.48.12}/prediction_market_agent_tooling/tools/tavily_storage/tavily_models.py +50 -3
- {prediction_market_agent_tooling-0.48.10 → prediction_market_agent_tooling-0.48.12}/prediction_market_agent_tooling/tools/tavily_storage/tavily_storage.py +15 -0
- {prediction_market_agent_tooling-0.48.10 → prediction_market_agent_tooling-0.48.12}/prediction_market_agent_tooling/tools/web3_utils.py +39 -8
- {prediction_market_agent_tooling-0.48.10 → prediction_market_agent_tooling-0.48.12}/pyproject.toml +1 -1
- prediction_market_agent_tooling-0.48.10/prediction_market_agent_tooling/deploy/betting_strategy.py +0 -62
- {prediction_market_agent_tooling-0.48.10 → prediction_market_agent_tooling-0.48.12}/LICENSE +0 -0
- {prediction_market_agent_tooling-0.48.10 → prediction_market_agent_tooling-0.48.12}/README.md +0 -0
- {prediction_market_agent_tooling-0.48.10 → prediction_market_agent_tooling-0.48.12}/prediction_market_agent_tooling/abis/depositablewrapper_erc20.abi.json +0 -0
- {prediction_market_agent_tooling-0.48.10 → prediction_market_agent_tooling-0.48.12}/prediction_market_agent_tooling/abis/erc20.abi.json +0 -0
- {prediction_market_agent_tooling-0.48.10 → prediction_market_agent_tooling-0.48.12}/prediction_market_agent_tooling/abis/erc4626.abi.json +0 -0
- {prediction_market_agent_tooling-0.48.10 → prediction_market_agent_tooling-0.48.12}/prediction_market_agent_tooling/abis/omen_dxdao.abi.json +0 -0
- {prediction_market_agent_tooling-0.48.10 → prediction_market_agent_tooling-0.48.12}/prediction_market_agent_tooling/abis/omen_fpmm.abi.json +0 -0
- {prediction_market_agent_tooling-0.48.10 → prediction_market_agent_tooling-0.48.12}/prediction_market_agent_tooling/abis/omen_fpmm_conditionaltokens.abi.json +0 -0
- {prediction_market_agent_tooling-0.48.10 → prediction_market_agent_tooling-0.48.12}/prediction_market_agent_tooling/abis/omen_fpmm_factory.abi.json +0 -0
- {prediction_market_agent_tooling-0.48.10 → prediction_market_agent_tooling-0.48.12}/prediction_market_agent_tooling/abis/omen_kleros.abi.json +0 -0
- {prediction_market_agent_tooling-0.48.10 → prediction_market_agent_tooling-0.48.12}/prediction_market_agent_tooling/abis/omen_oracle.abi.json +0 -0
- {prediction_market_agent_tooling-0.48.10 → prediction_market_agent_tooling-0.48.12}/prediction_market_agent_tooling/abis/omen_realitio.abi.json +0 -0
- {prediction_market_agent_tooling-0.48.10 → prediction_market_agent_tooling-0.48.12}/prediction_market_agent_tooling/abis/omen_thumbnailmapping.abi.json +0 -0
- {prediction_market_agent_tooling-0.48.10 → prediction_market_agent_tooling-0.48.12}/prediction_market_agent_tooling/abis/proxy.abi.json +0 -0
- {prediction_market_agent_tooling-0.48.10 → prediction_market_agent_tooling-0.48.12}/prediction_market_agent_tooling/benchmark/__init__.py +0 -0
- {prediction_market_agent_tooling-0.48.10 → prediction_market_agent_tooling-0.48.12}/prediction_market_agent_tooling/benchmark/agents.py +0 -0
- {prediction_market_agent_tooling-0.48.10 → prediction_market_agent_tooling-0.48.12}/prediction_market_agent_tooling/benchmark/benchmark.py +0 -0
- {prediction_market_agent_tooling-0.48.10 → prediction_market_agent_tooling-0.48.12}/prediction_market_agent_tooling/benchmark/utils.py +0 -0
- {prediction_market_agent_tooling-0.48.10 → prediction_market_agent_tooling-0.48.12}/prediction_market_agent_tooling/config.py +0 -0
- {prediction_market_agent_tooling-0.48.10 → prediction_market_agent_tooling-0.48.12}/prediction_market_agent_tooling/deploy/agent_example.py +0 -0
- {prediction_market_agent_tooling-0.48.10 → prediction_market_agent_tooling-0.48.12}/prediction_market_agent_tooling/deploy/constants.py +0 -0
- {prediction_market_agent_tooling-0.48.10 → prediction_market_agent_tooling-0.48.12}/prediction_market_agent_tooling/deploy/gcp/deploy.py +0 -0
- {prediction_market_agent_tooling-0.48.10 → prediction_market_agent_tooling-0.48.12}/prediction_market_agent_tooling/deploy/gcp/kubernetes_models.py +0 -0
- {prediction_market_agent_tooling-0.48.10 → prediction_market_agent_tooling-0.48.12}/prediction_market_agent_tooling/deploy/gcp/utils.py +0 -0
- {prediction_market_agent_tooling-0.48.10 → prediction_market_agent_tooling-0.48.12}/prediction_market_agent_tooling/gtypes.py +0 -0
- {prediction_market_agent_tooling-0.48.10 → prediction_market_agent_tooling-0.48.12}/prediction_market_agent_tooling/loggers.py +0 -0
- {prediction_market_agent_tooling-0.48.10 → prediction_market_agent_tooling-0.48.12}/prediction_market_agent_tooling/markets/categorize.py +0 -0
- {prediction_market_agent_tooling-0.48.10 → prediction_market_agent_tooling-0.48.12}/prediction_market_agent_tooling/markets/manifold/__init__.py +0 -0
- {prediction_market_agent_tooling-0.48.10 → prediction_market_agent_tooling-0.48.12}/prediction_market_agent_tooling/markets/manifold/api.py +0 -0
- {prediction_market_agent_tooling-0.48.10 → prediction_market_agent_tooling-0.48.12}/prediction_market_agent_tooling/markets/manifold/utils.py +0 -0
- {prediction_market_agent_tooling-0.48.10 → prediction_market_agent_tooling-0.48.12}/prediction_market_agent_tooling/markets/markets.py +0 -0
- {prediction_market_agent_tooling-0.48.10 → prediction_market_agent_tooling-0.48.12}/prediction_market_agent_tooling/markets/metaculus/api.py +0 -0
- {prediction_market_agent_tooling-0.48.10 → prediction_market_agent_tooling-0.48.12}/prediction_market_agent_tooling/markets/metaculus/data_models.py +0 -0
- {prediction_market_agent_tooling-0.48.10 → prediction_market_agent_tooling-0.48.12}/prediction_market_agent_tooling/markets/omen/__init__.py +0 -0
- {prediction_market_agent_tooling-0.48.10 → prediction_market_agent_tooling-0.48.12}/prediction_market_agent_tooling/markets/omen/omen_contracts.py +0 -0
- {prediction_market_agent_tooling-0.48.10 → prediction_market_agent_tooling-0.48.12}/prediction_market_agent_tooling/markets/omen/omen_resolving.py +0 -0
- {prediction_market_agent_tooling-0.48.10 → prediction_market_agent_tooling-0.48.12}/prediction_market_agent_tooling/markets/polymarket/api.py +0 -0
- {prediction_market_agent_tooling-0.48.10 → prediction_market_agent_tooling-0.48.12}/prediction_market_agent_tooling/markets/polymarket/data_models.py +0 -0
- {prediction_market_agent_tooling-0.48.10 → prediction_market_agent_tooling-0.48.12}/prediction_market_agent_tooling/markets/polymarket/data_models_web.py +0 -0
- {prediction_market_agent_tooling-0.48.10 → prediction_market_agent_tooling-0.48.12}/prediction_market_agent_tooling/markets/polymarket/utils.py +0 -0
- {prediction_market_agent_tooling-0.48.10 → prediction_market_agent_tooling-0.48.12}/prediction_market_agent_tooling/monitor/markets/manifold.py +0 -0
- {prediction_market_agent_tooling-0.48.10 → prediction_market_agent_tooling-0.48.12}/prediction_market_agent_tooling/monitor/markets/metaculus.py +0 -0
- {prediction_market_agent_tooling-0.48.10 → prediction_market_agent_tooling-0.48.12}/prediction_market_agent_tooling/monitor/markets/omen.py +0 -0
- {prediction_market_agent_tooling-0.48.10 → prediction_market_agent_tooling-0.48.12}/prediction_market_agent_tooling/monitor/markets/polymarket.py +0 -0
- {prediction_market_agent_tooling-0.48.10 → prediction_market_agent_tooling-0.48.12}/prediction_market_agent_tooling/monitor/monitor.py +0 -0
- {prediction_market_agent_tooling-0.48.10 → prediction_market_agent_tooling-0.48.12}/prediction_market_agent_tooling/monitor/monitor_app.py +0 -0
- {prediction_market_agent_tooling-0.48.10 → prediction_market_agent_tooling-0.48.12}/prediction_market_agent_tooling/monitor/monitor_settings.py +0 -0
- {prediction_market_agent_tooling-0.48.10 → prediction_market_agent_tooling-0.48.12}/prediction_market_agent_tooling/py.typed +0 -0
- {prediction_market_agent_tooling-0.48.10 → prediction_market_agent_tooling-0.48.12}/prediction_market_agent_tooling/tools/balances.py +0 -0
- {prediction_market_agent_tooling-0.48.10 → prediction_market_agent_tooling-0.48.12}/prediction_market_agent_tooling/tools/betting_strategies/kelly_criterion.py +0 -0
- {prediction_market_agent_tooling-0.48.10 → prediction_market_agent_tooling-0.48.12}/prediction_market_agent_tooling/tools/betting_strategies/market_moving.py +0 -0
- {prediction_market_agent_tooling-0.48.10 → prediction_market_agent_tooling-0.48.12}/prediction_market_agent_tooling/tools/betting_strategies/minimum_bet_to_win.py +0 -0
- {prediction_market_agent_tooling-0.48.10 → prediction_market_agent_tooling-0.48.12}/prediction_market_agent_tooling/tools/betting_strategies/stretch_bet_between.py +0 -0
- {prediction_market_agent_tooling-0.48.10 → prediction_market_agent_tooling-0.48.12}/prediction_market_agent_tooling/tools/cache.py +0 -0
- {prediction_market_agent_tooling-0.48.10 → prediction_market_agent_tooling-0.48.12}/prediction_market_agent_tooling/tools/costs.py +0 -0
- {prediction_market_agent_tooling-0.48.10 → prediction_market_agent_tooling-0.48.12}/prediction_market_agent_tooling/tools/gnosis_rpc.py +0 -0
- {prediction_market_agent_tooling-0.48.10 → prediction_market_agent_tooling-0.48.12}/prediction_market_agent_tooling/tools/google.py +0 -0
- {prediction_market_agent_tooling-0.48.10 → prediction_market_agent_tooling-0.48.12}/prediction_market_agent_tooling/tools/hexbytes_custom.py +0 -0
- {prediction_market_agent_tooling-0.48.10 → prediction_market_agent_tooling-0.48.12}/prediction_market_agent_tooling/tools/image_gen/image_gen.py +0 -0
- {prediction_market_agent_tooling-0.48.10 → prediction_market_agent_tooling-0.48.12}/prediction_market_agent_tooling/tools/image_gen/market_thumbnail_gen.py +0 -0
- {prediction_market_agent_tooling-0.48.10 → prediction_market_agent_tooling-0.48.12}/prediction_market_agent_tooling/tools/is_predictable.py +0 -0
- {prediction_market_agent_tooling-0.48.10 → prediction_market_agent_tooling-0.48.12}/prediction_market_agent_tooling/tools/langfuse_.py +0 -0
- {prediction_market_agent_tooling-0.48.10 → prediction_market_agent_tooling-0.48.12}/prediction_market_agent_tooling/tools/parallelism.py +0 -0
- {prediction_market_agent_tooling-0.48.10 → prediction_market_agent_tooling-0.48.12}/prediction_market_agent_tooling/tools/safe.py +0 -0
- {prediction_market_agent_tooling-0.48.10 → prediction_market_agent_tooling-0.48.12}/prediction_market_agent_tooling/tools/singleton.py +0 -0
- {prediction_market_agent_tooling-0.48.10 → prediction_market_agent_tooling-0.48.12}/prediction_market_agent_tooling/tools/streamlit_user_login.py +0 -0
- {prediction_market_agent_tooling-0.48.10 → prediction_market_agent_tooling-0.48.12}/prediction_market_agent_tooling/tools/utils.py +0 -0
@@ -15,6 +15,7 @@ from prediction_market_agent_tooling.config import APIKeys
|
|
15
15
|
from prediction_market_agent_tooling.deploy.betting_strategy import (
|
16
16
|
BettingStrategy,
|
17
17
|
MaxAccuracyBettingStrategy,
|
18
|
+
TradeType,
|
18
19
|
)
|
19
20
|
from prediction_market_agent_tooling.deploy.constants import (
|
20
21
|
MARKET_TYPE_KEY,
|
@@ -37,10 +38,9 @@ from prediction_market_agent_tooling.markets.agent_market import (
|
|
37
38
|
SortBy,
|
38
39
|
)
|
39
40
|
from prediction_market_agent_tooling.markets.data_models import (
|
40
|
-
|
41
|
+
Position,
|
41
42
|
ProbabilisticAnswer,
|
42
|
-
|
43
|
-
TokenAmountAndDirection,
|
43
|
+
Trade,
|
44
44
|
)
|
45
45
|
from prediction_market_agent_tooling.markets.markets import (
|
46
46
|
MarketType,
|
@@ -110,7 +110,7 @@ class OutOfFundsError(ValueError):
|
|
110
110
|
|
111
111
|
class ProcessedMarket(BaseModel):
|
112
112
|
answer: ProbabilisticAnswer
|
113
|
-
|
113
|
+
trades: list[Trade]
|
114
114
|
|
115
115
|
|
116
116
|
class AnsweredEnum(str, Enum):
|
@@ -283,6 +283,7 @@ class DeployableTraderAgent(DeployableAgent):
|
|
283
283
|
min_required_balance_to_operate: xDai | None = xdai_type(1)
|
284
284
|
min_balance_to_keep_in_native_currency: xDai | None = xdai_type(0.1)
|
285
285
|
strategy: BettingStrategy = MaxAccuracyBettingStrategy()
|
286
|
+
allow_opposite_bets: bool = False
|
286
287
|
|
287
288
|
def __init__(
|
288
289
|
self,
|
@@ -298,8 +299,8 @@ class DeployableTraderAgent(DeployableAgent):
|
|
298
299
|
self.have_bet_on_market_since = observe()(self.have_bet_on_market_since) # type: ignore[method-assign]
|
299
300
|
self.verify_market = observe()(self.verify_market) # type: ignore[method-assign]
|
300
301
|
self.answer_binary_market = observe()(self.answer_binary_market) # type: ignore[method-assign]
|
301
|
-
self.calculate_bet_amount_and_direction = observe()(self.calculate_bet_amount_and_direction) # type: ignore[method-assign]
|
302
302
|
self.process_market = observe()(self.process_market) # type: ignore[method-assign]
|
303
|
+
self.build_trades = observe()(self.build_trades) # type: ignore[method-assign]
|
303
304
|
|
304
305
|
def update_langfuse_trace_by_market(
|
305
306
|
self, market_type: MarketType, market: AgentMarket
|
@@ -314,18 +315,6 @@ class DeployableTraderAgent(DeployableAgent):
|
|
314
315
|
},
|
315
316
|
)
|
316
317
|
|
317
|
-
def calculate_bet_amount_and_direction(
|
318
|
-
self, answer: ProbabilisticAnswer, market: AgentMarket
|
319
|
-
) -> TokenAmountAndDirection:
|
320
|
-
amount_and_direction = self.strategy.calculate_bet_amount_and_direction(
|
321
|
-
answer, market
|
322
|
-
)
|
323
|
-
if amount_and_direction.currency != market.currency:
|
324
|
-
raise ValueError(
|
325
|
-
f"Currency mismatch. Strategy yields {amount_and_direction.currency}, market has currency {market.currency}"
|
326
|
-
)
|
327
|
-
return amount_and_direction
|
328
|
-
|
329
318
|
def update_langfuse_trace_by_processed_market(
|
330
319
|
self, market_type: MarketType, processed_market: ProcessedMarket | None
|
331
320
|
) -> None:
|
@@ -416,13 +405,26 @@ class DeployableTraderAgent(DeployableAgent):
|
|
416
405
|
)
|
417
406
|
return available_markets
|
418
407
|
|
408
|
+
def build_trades(
|
409
|
+
self,
|
410
|
+
market: AgentMarket,
|
411
|
+
answer: ProbabilisticAnswer,
|
412
|
+
existing_position: Position | None,
|
413
|
+
) -> list[Trade]:
|
414
|
+
trades = self.strategy.calculate_trades(existing_position, answer, market)
|
415
|
+
BettingStrategy.assert_trades_currency_match_markets(market, trades)
|
416
|
+
return trades
|
417
|
+
|
419
418
|
def before_process_market(
|
420
419
|
self, market_type: MarketType, market: AgentMarket
|
421
420
|
) -> None:
|
422
421
|
self.update_langfuse_trace_by_market(market_type, market)
|
423
422
|
|
424
423
|
def process_market(
|
425
|
-
self,
|
424
|
+
self,
|
425
|
+
market_type: MarketType,
|
426
|
+
market: AgentMarket,
|
427
|
+
verify_market: bool = True,
|
426
428
|
) -> ProcessedMarket | None:
|
427
429
|
self.before_process_market(market_type, market)
|
428
430
|
|
@@ -438,26 +440,26 @@ class DeployableTraderAgent(DeployableAgent):
|
|
438
440
|
self.update_langfuse_trace_by_processed_market(market_type, None)
|
439
441
|
return None
|
440
442
|
|
441
|
-
|
443
|
+
existing_position = market.get_position(user_id=APIKeys().bet_from_address)
|
444
|
+
trades = self.build_trades(
|
445
|
+
market=market, answer=answer, existing_position=existing_position
|
446
|
+
)
|
442
447
|
|
443
448
|
if self.place_bet:
|
444
|
-
|
445
|
-
f"
|
446
|
-
|
447
|
-
|
448
|
-
|
449
|
-
|
450
|
-
|
451
|
-
|
452
|
-
|
453
|
-
|
449
|
+
for trade in trades:
|
450
|
+
logger.info(f"Executing trade {trade}")
|
451
|
+
|
452
|
+
match trade.trade_type:
|
453
|
+
case TradeType.BUY:
|
454
|
+
market.buy_tokens(outcome=trade.outcome, amount=trade.amount)
|
455
|
+
case TradeType.SELL:
|
456
|
+
market.sell_tokens(outcome=trade.outcome, amount=trade.amount)
|
457
|
+
case _:
|
458
|
+
raise ValueError(f"Unexpected trade type {trade.trade_type}.")
|
454
459
|
|
455
460
|
self.after_process_market(market_type, market)
|
456
461
|
|
457
|
-
processed_market = ProcessedMarket(
|
458
|
-
answer=answer,
|
459
|
-
amount=amount_and_direction,
|
460
|
-
)
|
462
|
+
processed_market = ProcessedMarket(answer=answer, trades=trades)
|
461
463
|
self.update_langfuse_trace_by_processed_market(market_type, processed_market)
|
462
464
|
|
463
465
|
return processed_market
|
prediction_market_agent_tooling-0.48.12/prediction_market_agent_tooling/deploy/betting_strategy.py
ADDED
@@ -0,0 +1,187 @@
|
|
1
|
+
from abc import ABC, abstractmethod
|
2
|
+
|
3
|
+
from prediction_market_agent_tooling.markets.agent_market import AgentMarket
|
4
|
+
from prediction_market_agent_tooling.markets.data_models import (
|
5
|
+
Currency,
|
6
|
+
Position,
|
7
|
+
ProbabilisticAnswer,
|
8
|
+
TokenAmount,
|
9
|
+
Trade,
|
10
|
+
TradeType,
|
11
|
+
)
|
12
|
+
from prediction_market_agent_tooling.markets.omen.data_models import get_boolean_outcome
|
13
|
+
from prediction_market_agent_tooling.tools.betting_strategies.kelly_criterion import (
|
14
|
+
get_kelly_bet,
|
15
|
+
)
|
16
|
+
|
17
|
+
|
18
|
+
class BettingStrategy(ABC):
|
19
|
+
@abstractmethod
|
20
|
+
def calculate_trades(
|
21
|
+
self,
|
22
|
+
existing_position: Position | None,
|
23
|
+
answer: ProbabilisticAnswer,
|
24
|
+
market: AgentMarket,
|
25
|
+
) -> list[Trade]:
|
26
|
+
pass
|
27
|
+
|
28
|
+
def build_zero_token_amount(self, currency: Currency) -> TokenAmount:
|
29
|
+
return TokenAmount(amount=0, currency=currency)
|
30
|
+
|
31
|
+
@abstractmethod
|
32
|
+
def adjust_bet_amount(
|
33
|
+
self, existing_position: Position | None, market: AgentMarket
|
34
|
+
) -> float:
|
35
|
+
pass
|
36
|
+
|
37
|
+
@staticmethod
|
38
|
+
def assert_trades_currency_match_markets(
|
39
|
+
market: AgentMarket, trades: list[Trade]
|
40
|
+
) -> None:
|
41
|
+
currencies_match = all([t.amount.currency == market.currency for t in trades])
|
42
|
+
if not currencies_match:
|
43
|
+
raise ValueError(
|
44
|
+
"Cannot handle trades with currencies that deviate from market's currency"
|
45
|
+
)
|
46
|
+
|
47
|
+
def _build_rebalance_trades_from_positions(
|
48
|
+
self,
|
49
|
+
existing_position: Position | None,
|
50
|
+
target_position: Position,
|
51
|
+
market: AgentMarket,
|
52
|
+
) -> list[Trade]:
|
53
|
+
"""
|
54
|
+
This helper method builds trades by rebalancing token allocations to each outcome.
|
55
|
+
For example, if we have an existing position with 10 tokens in outcome 0 and 5 in outcome 1,
|
56
|
+
and our target position is 20 tokens in outcome 0 and 0 in outcome 1, we would return these trades:
|
57
|
+
trades = [
|
58
|
+
Trade(outcome=0, amount=10, trade_type=TradeType.BUY),
|
59
|
+
Trade(outcome=1, amount=5, trade_type=TradeType.SELL)
|
60
|
+
]
|
61
|
+
Note that we order the trades to first buy then sell, in order to minimally tilt the odds so that
|
62
|
+
sell price is higher.
|
63
|
+
"""
|
64
|
+
trades = []
|
65
|
+
for outcome in [
|
66
|
+
market.get_outcome_str_from_bool(True),
|
67
|
+
market.get_outcome_str_from_bool(False),
|
68
|
+
]:
|
69
|
+
outcome_bool = get_boolean_outcome(outcome)
|
70
|
+
prev_amount: TokenAmount = (
|
71
|
+
existing_position.amounts[outcome]
|
72
|
+
if existing_position and outcome in existing_position.amounts
|
73
|
+
else self.build_zero_token_amount(currency=market.currency)
|
74
|
+
)
|
75
|
+
new_amount: TokenAmount = target_position.amounts.get(
|
76
|
+
outcome, self.build_zero_token_amount(currency=market.currency)
|
77
|
+
)
|
78
|
+
|
79
|
+
if prev_amount.currency != new_amount.currency:
|
80
|
+
raise ValueError("Cannot handle positions with different currencies")
|
81
|
+
diff_amount = prev_amount.amount - new_amount.amount
|
82
|
+
if diff_amount == 0:
|
83
|
+
continue
|
84
|
+
trade_type = TradeType.SELL if diff_amount < 0 else TradeType.BUY
|
85
|
+
trade = Trade(
|
86
|
+
amount=TokenAmount(amount=abs(diff_amount), currency=market.currency),
|
87
|
+
outcome=outcome_bool,
|
88
|
+
trade_type=trade_type,
|
89
|
+
)
|
90
|
+
|
91
|
+
trades.append(trade)
|
92
|
+
|
93
|
+
# Sort inplace with SELL last
|
94
|
+
trades.sort(key=lambda t: t.trade_type == TradeType.SELL)
|
95
|
+
BettingStrategy.assert_trades_currency_match_markets(market, trades)
|
96
|
+
return trades
|
97
|
+
|
98
|
+
|
99
|
+
class MaxAccuracyBettingStrategy(BettingStrategy):
|
100
|
+
def adjust_bet_amount(
|
101
|
+
self, existing_position: Position | None, market: AgentMarket
|
102
|
+
) -> float:
|
103
|
+
existing_position_total_amount = (
|
104
|
+
existing_position.total_amount.amount if existing_position else 0
|
105
|
+
)
|
106
|
+
bet_amount = (
|
107
|
+
market.get_tiny_bet_amount().amount
|
108
|
+
if self.bet_amount is None
|
109
|
+
else self.bet_amount
|
110
|
+
)
|
111
|
+
return bet_amount + existing_position_total_amount
|
112
|
+
|
113
|
+
def __init__(self, bet_amount: float | None = None):
|
114
|
+
self.bet_amount = bet_amount
|
115
|
+
|
116
|
+
def calculate_trades(
|
117
|
+
self,
|
118
|
+
existing_position: Position | None,
|
119
|
+
answer: ProbabilisticAnswer,
|
120
|
+
market: AgentMarket,
|
121
|
+
) -> list[Trade]:
|
122
|
+
adjusted_bet_amount = self.adjust_bet_amount(existing_position, market)
|
123
|
+
|
124
|
+
direction = self.calculate_direction(market.current_p_yes, answer.p_yes)
|
125
|
+
|
126
|
+
amounts = {
|
127
|
+
market.get_outcome_str_from_bool(direction): TokenAmount(
|
128
|
+
amount=adjusted_bet_amount,
|
129
|
+
currency=market.currency,
|
130
|
+
),
|
131
|
+
}
|
132
|
+
target_position = Position(market_id=market.id, amounts=amounts)
|
133
|
+
trades = self._build_rebalance_trades_from_positions(
|
134
|
+
existing_position, target_position, market=market
|
135
|
+
)
|
136
|
+
return trades
|
137
|
+
|
138
|
+
@staticmethod
|
139
|
+
def calculate_direction(market_p_yes: float, estimate_p_yes: float) -> bool:
|
140
|
+
return estimate_p_yes >= 0.5
|
141
|
+
|
142
|
+
|
143
|
+
class MaxExpectedValueBettingStrategy(MaxAccuracyBettingStrategy):
|
144
|
+
@staticmethod
|
145
|
+
def calculate_direction(market_p_yes: float, estimate_p_yes: float) -> bool:
|
146
|
+
# If estimate_p_yes >= market.current_p_yes, then bet TRUE, else bet FALSE.
|
147
|
+
# This is equivalent to saying EXPECTED_VALUE = (estimate_p_yes * num_tokens_obtained_by_betting_yes) -
|
148
|
+
# ((1 - estimate_p_yes) * num_tokens_obtained_by_betting_no) >= 0
|
149
|
+
return estimate_p_yes >= market_p_yes
|
150
|
+
|
151
|
+
|
152
|
+
class KellyBettingStrategy(BettingStrategy):
|
153
|
+
def __init__(self, max_bet_amount: float = 10):
|
154
|
+
self.max_bet_amount = max_bet_amount
|
155
|
+
|
156
|
+
def adjust_bet_amount(
|
157
|
+
self, existing_position: Position | None, market: AgentMarket
|
158
|
+
) -> float:
|
159
|
+
existing_position_total_amount = (
|
160
|
+
existing_position.total_amount.amount if existing_position else 0
|
161
|
+
)
|
162
|
+
return self.max_bet_amount + existing_position_total_amount
|
163
|
+
|
164
|
+
def calculate_trades(
|
165
|
+
self,
|
166
|
+
existing_position: Position | None,
|
167
|
+
answer: ProbabilisticAnswer,
|
168
|
+
market: AgentMarket,
|
169
|
+
) -> list[Trade]:
|
170
|
+
adjusted_bet_amount = self.adjust_bet_amount(existing_position, market)
|
171
|
+
kelly_bet = get_kelly_bet(
|
172
|
+
adjusted_bet_amount,
|
173
|
+
market.current_p_yes,
|
174
|
+
answer.p_yes,
|
175
|
+
answer.confidence,
|
176
|
+
)
|
177
|
+
|
178
|
+
amounts = {
|
179
|
+
market.get_outcome_str_from_bool(kelly_bet.direction): TokenAmount(
|
180
|
+
amount=kelly_bet.size, currency=market.currency
|
181
|
+
),
|
182
|
+
}
|
183
|
+
target_position = Position(market_id=market.id, amounts=amounts)
|
184
|
+
trades = self._build_rebalance_trades_from_positions(
|
185
|
+
existing_position, target_position, market=market
|
186
|
+
)
|
187
|
+
return trades
|
@@ -4,9 +4,10 @@ from enum import Enum
|
|
4
4
|
|
5
5
|
from eth_typing import ChecksumAddress
|
6
6
|
from pydantic import BaseModel, field_validator
|
7
|
+
from pydantic_core.core_schema import FieldValidationInfo
|
7
8
|
|
8
9
|
from prediction_market_agent_tooling.config import APIKeys
|
9
|
-
from prediction_market_agent_tooling.gtypes import Probability
|
10
|
+
from prediction_market_agent_tooling.gtypes import OutcomeStr, Probability
|
10
11
|
from prediction_market_agent_tooling.markets.data_models import (
|
11
12
|
Bet,
|
12
13
|
BetAmount,
|
@@ -49,6 +50,9 @@ class AgentMarket(BaseModel):
|
|
49
50
|
question: str
|
50
51
|
description: str | None
|
51
52
|
outcomes: list[str]
|
53
|
+
outcome_token_pool: dict[
|
54
|
+
str, float
|
55
|
+
] | None # Should be in currency of `currency` above.
|
52
56
|
resolution: Resolution | None
|
53
57
|
created_time: datetime | None
|
54
58
|
close_time: datetime | None
|
@@ -63,6 +67,22 @@ class AgentMarket(BaseModel):
|
|
63
67
|
add_utc_timezone_validator
|
64
68
|
)
|
65
69
|
|
70
|
+
@field_validator("outcome_token_pool")
|
71
|
+
def validate_outcome_token_pool(
|
72
|
+
cls,
|
73
|
+
outcome_token_pool: dict[str, float] | None,
|
74
|
+
info: FieldValidationInfo,
|
75
|
+
) -> dict[str, float] | None:
|
76
|
+
outcomes: list[str] = check_not_none(info.data.get("outcomes"))
|
77
|
+
if outcome_token_pool is not None:
|
78
|
+
outcome_keys = set(outcome_token_pool.keys())
|
79
|
+
expected_keys = set(outcomes)
|
80
|
+
if outcome_keys != expected_keys:
|
81
|
+
raise ValueError(
|
82
|
+
f"Keys of outcome_token_pool ({outcome_keys}) do not match outcomes ({expected_keys})."
|
83
|
+
)
|
84
|
+
return outcome_token_pool
|
85
|
+
|
66
86
|
@property
|
67
87
|
def current_p_no(self) -> Probability:
|
68
88
|
return Probability(1 - self.current_p_yes)
|
@@ -133,10 +153,19 @@ class AgentMarket(BaseModel):
|
|
133
153
|
def get_bet_amount(self, amount: float) -> BetAmount:
|
134
154
|
return BetAmount(amount=amount, currency=self.currency)
|
135
155
|
|
156
|
+
@classmethod
|
157
|
+
def get_liquidatable_amount(cls) -> BetAmount:
|
158
|
+
tiny_amount = cls.get_tiny_bet_amount()
|
159
|
+
tiny_amount.amount /= 10
|
160
|
+
return tiny_amount
|
161
|
+
|
136
162
|
@classmethod
|
137
163
|
def get_tiny_bet_amount(cls) -> BetAmount:
|
138
164
|
raise NotImplementedError("Subclasses must implement this method")
|
139
165
|
|
166
|
+
def liquidate_existing_positions(self, outcome: bool) -> None:
|
167
|
+
raise NotImplementedError("Subclasses must implement this method")
|
168
|
+
|
140
169
|
def place_bet(self, outcome: bool, amount: BetAmount) -> None:
|
141
170
|
raise NotImplementedError("Subclasses must implement this method")
|
142
171
|
|
@@ -190,6 +219,9 @@ class AgentMarket(BaseModel):
|
|
190
219
|
def has_unsuccessful_resolution(self) -> bool:
|
191
220
|
return self.resolution in [Resolution.CANCEL, Resolution.MKT]
|
192
221
|
|
222
|
+
def get_outcome_str_from_bool(self, outcome: bool) -> OutcomeStr:
|
223
|
+
raise NotImplementedError("Subclasses must implement this method")
|
224
|
+
|
193
225
|
def get_outcome_str(self, outcome_index: int) -> str:
|
194
226
|
try:
|
195
227
|
return self.outcomes[outcome_index]
|
@@ -207,6 +239,9 @@ class AgentMarket(BaseModel):
|
|
207
239
|
def get_token_balance(self, user_id: str, outcome: str) -> TokenAmount:
|
208
240
|
raise NotImplementedError("Subclasses must implement this method")
|
209
241
|
|
242
|
+
def get_position(self, user_id: str) -> Position | None:
|
243
|
+
raise NotImplementedError("Subclasses must implement this method")
|
244
|
+
|
210
245
|
@classmethod
|
211
246
|
def get_positions(
|
212
247
|
cls, user_id: str, liquid_only: bool = False, larger_than: float = 0
|
@@ -236,3 +271,12 @@ class AgentMarket(BaseModel):
|
|
236
271
|
@classmethod
|
237
272
|
def get_user_url(cls, keys: APIKeys) -> str:
|
238
273
|
raise NotImplementedError("Subclasses must implement this method")
|
274
|
+
|
275
|
+
def has_token_pool(self) -> bool:
|
276
|
+
return self.outcome_token_pool is not None
|
277
|
+
|
278
|
+
def get_pool_tokens(self, outcome: str) -> float:
|
279
|
+
if not self.outcome_token_pool:
|
280
|
+
raise ValueError("Outcome token pool is not available.")
|
281
|
+
|
282
|
+
return self.outcome_token_pool[outcome]
|
@@ -115,3 +115,14 @@ class Position(BaseModel):
|
|
115
115
|
for outcome, amount in self.amounts.items()
|
116
116
|
)
|
117
117
|
return f"Position for market id {self.market_id}: {amounts_str}"
|
118
|
+
|
119
|
+
|
120
|
+
class TradeType(str, Enum):
|
121
|
+
SELL = "sell"
|
122
|
+
BUY = "buy"
|
123
|
+
|
124
|
+
|
125
|
+
class Trade(BaseModel):
|
126
|
+
trade_type: TradeType
|
127
|
+
outcome: bool
|
128
|
+
amount: TokenAmount
|
@@ -19,6 +19,12 @@ class ManifoldPool(BaseModel):
|
|
19
19
|
NO: float
|
20
20
|
YES: float
|
21
21
|
|
22
|
+
def size_for_outcome(self, outcome: str) -> float:
|
23
|
+
if hasattr(self, outcome):
|
24
|
+
return float(getattr(self, outcome))
|
25
|
+
else:
|
26
|
+
should_not_happen(f"Unexpected outcome string, '{outcome}'.")
|
27
|
+
|
22
28
|
|
23
29
|
class ManifoldAnswersMode(str, Enum):
|
24
30
|
ANYONE = "ANYONE"
|
@@ -44,6 +44,10 @@ def get_boolean_outcome(outcome_str: str) -> bool:
|
|
44
44
|
raise ValueError(f"Outcome `{outcome_str}` is not a valid boolean outcome.")
|
45
45
|
|
46
46
|
|
47
|
+
def get_bet_outcome(binary_outcome: bool) -> str:
|
48
|
+
return OMEN_TRUE_OUTCOME if binary_outcome else OMEN_FALSE_OUTCOME
|
49
|
+
|
50
|
+
|
47
51
|
class Condition(BaseModel):
|
48
52
|
id: HexBytes
|
49
53
|
outcomeSlotCount: int
|
@@ -40,6 +40,8 @@ from prediction_market_agent_tooling.markets.omen.data_models import (
|
|
40
40
|
OmenBet,
|
41
41
|
OmenMarket,
|
42
42
|
OmenUserPosition,
|
43
|
+
get_bet_outcome,
|
44
|
+
get_boolean_outcome,
|
43
45
|
)
|
44
46
|
from prediction_market_agent_tooling.markets.omen.omen_contracts import (
|
45
47
|
OMEN_DEFAULT_MARKET_FEE,
|
@@ -75,6 +77,7 @@ from prediction_market_agent_tooling.tools.web3_utils import (
|
|
75
77
|
)
|
76
78
|
|
77
79
|
OMEN_DEFAULT_REALITIO_BOND_VALUE = xdai_type(0.01)
|
80
|
+
OMEN_TINY_BET_AMOUNT = xdai_type(0.00001)
|
78
81
|
|
79
82
|
|
80
83
|
class OmenAgentMarket(AgentMarket):
|
@@ -147,7 +150,41 @@ class OmenAgentMarket(AgentMarket):
|
|
147
150
|
|
148
151
|
@classmethod
|
149
152
|
def get_tiny_bet_amount(cls) -> BetAmount:
|
150
|
-
return BetAmount(amount=
|
153
|
+
return BetAmount(amount=OMEN_TINY_BET_AMOUNT, currency=cls.currency)
|
154
|
+
|
155
|
+
def liquidate_existing_positions(
|
156
|
+
self,
|
157
|
+
bet_outcome: bool,
|
158
|
+
web3: Web3 | None = None,
|
159
|
+
api_keys: APIKeys | None = None,
|
160
|
+
larger_than: float | None = None,
|
161
|
+
) -> None:
|
162
|
+
"""
|
163
|
+
Liquidates all previously existing positions.
|
164
|
+
Returns the amount in collateral obtained by selling the positions.
|
165
|
+
"""
|
166
|
+
api_keys = api_keys if api_keys is not None else APIKeys()
|
167
|
+
better_address = api_keys.bet_from_address
|
168
|
+
larger_than = (
|
169
|
+
larger_than
|
170
|
+
if larger_than is not None
|
171
|
+
else self.get_liquidatable_amount().amount
|
172
|
+
)
|
173
|
+
prev_positions_for_market = self.get_positions(
|
174
|
+
user_id=better_address, liquid_only=True, larger_than=larger_than
|
175
|
+
)
|
176
|
+
|
177
|
+
for prev_position in prev_positions_for_market:
|
178
|
+
for position_outcome, token_amount in prev_position.amounts.items():
|
179
|
+
position_outcome_bool = get_boolean_outcome(position_outcome)
|
180
|
+
if position_outcome_bool != bet_outcome:
|
181
|
+
# We keep it as collateral since we want to place a bet immediately after this function.
|
182
|
+
self.sell_tokens(
|
183
|
+
outcome=position_outcome_bool,
|
184
|
+
amount=token_amount,
|
185
|
+
auto_withdraw=False,
|
186
|
+
web3=web3,
|
187
|
+
)
|
151
188
|
|
152
189
|
def place_bet(
|
153
190
|
self,
|
@@ -310,6 +347,10 @@ class OmenAgentMarket(AgentMarket):
|
|
310
347
|
volume=wei_to_xdai(model.collateralVolume),
|
311
348
|
close_time=model.close_time,
|
312
349
|
fee=float(wei_to_xdai(model.fee)) if model.fee is not None else 0.0,
|
350
|
+
outcome_token_pool={
|
351
|
+
model.outcomes[i]: wei_to_xdai(Wei(model.outcomeTokenAmounts[i]))
|
352
|
+
for i in range(len(model.outcomes))
|
353
|
+
},
|
313
354
|
)
|
314
355
|
|
315
356
|
@staticmethod
|
@@ -381,6 +422,11 @@ class OmenAgentMarket(AgentMarket):
|
|
381
422
|
cls.get_outcome_str(cls.index_set_to_outcome_index(index_set))
|
382
423
|
)
|
383
424
|
|
425
|
+
def get_outcome_str_from_bool(self, outcome: bool) -> OutcomeStr:
|
426
|
+
return (
|
427
|
+
OutcomeStr(OMEN_TRUE_OUTCOME) if outcome else OutcomeStr(OMEN_FALSE_OUTCOME)
|
428
|
+
)
|
429
|
+
|
384
430
|
def get_token_balance(
|
385
431
|
self, user_id: str, outcome: str, web3: Web3 | None = None
|
386
432
|
) -> TokenAmount:
|
@@ -393,6 +439,18 @@ class OmenAgentMarket(AgentMarket):
|
|
393
439
|
currency=self.currency,
|
394
440
|
)
|
395
441
|
|
442
|
+
def get_position(self, user_id: str) -> Position | None:
|
443
|
+
liquidatable_amount = self.get_liquidatable_amount()
|
444
|
+
existing_positions = self.get_positions(
|
445
|
+
user_id=user_id,
|
446
|
+
liquid_only=True,
|
447
|
+
larger_than=liquidatable_amount.amount,
|
448
|
+
)
|
449
|
+
existing_position = next(
|
450
|
+
iter([i for i in existing_positions if i.market_id == self.id]), None
|
451
|
+
)
|
452
|
+
return existing_position
|
453
|
+
|
396
454
|
@classmethod
|
397
455
|
def get_positions(
|
398
456
|
cls,
|
@@ -553,12 +611,15 @@ def omen_buy_outcome_tx(
|
|
553
611
|
market_contract: OmenFixedProductMarketMakerContract = market.get_contract()
|
554
612
|
collateral_token_contract = market_contract.get_collateral_token_contract()
|
555
613
|
|
614
|
+
# In case of ERC4626, obtained (for example) sDai out of xDai could be lower than the `amount_wei`, so we need to handle it.
|
615
|
+
amount_wei_to_buy = collateral_token_contract.get_in_shares(amount_wei, web3)
|
616
|
+
|
556
617
|
# Get the index of the outcome we want to buy.
|
557
618
|
outcome_index: int = market.get_outcome_index(outcome)
|
558
619
|
|
559
620
|
# Calculate the amount of shares we will get for the given investment amount.
|
560
621
|
expected_shares = market_contract.calcBuyAmount(
|
561
|
-
|
622
|
+
amount_wei_to_buy, outcome_index, web3=web3
|
562
623
|
)
|
563
624
|
# Allow 1% slippage.
|
564
625
|
expected_shares = remove_fraction(expected_shares, 0.01)
|
@@ -566,11 +627,12 @@ def omen_buy_outcome_tx(
|
|
566
627
|
collateral_token_contract.approve(
|
567
628
|
api_keys=api_keys,
|
568
629
|
for_address=market_contract.address,
|
569
|
-
amount_wei=
|
630
|
+
amount_wei=amount_wei_to_buy,
|
570
631
|
web3=web3,
|
571
632
|
)
|
572
633
|
|
573
634
|
if auto_deposit:
|
635
|
+
# In auto-depositing, we need to deposit the original `amount_wei`, e.g. we can deposit 2 xDai, but receive 1.8 sDai, so for the bet we will use `amount_wei_to_buy`.
|
574
636
|
auto_deposit_collateral_token(
|
575
637
|
collateral_token_contract, amount_wei, api_keys, web3
|
576
638
|
)
|
@@ -578,7 +640,7 @@ def omen_buy_outcome_tx(
|
|
578
640
|
# Buy shares using the deposited xDai in the collateral token.
|
579
641
|
market_contract.buy(
|
580
642
|
api_keys=api_keys,
|
581
|
-
amount_wei=
|
643
|
+
amount_wei=amount_wei_to_buy,
|
582
644
|
outcome_index=outcome_index,
|
583
645
|
min_outcome_tokens_to_buy=expected_shares,
|
584
646
|
web3=web3,
|
@@ -597,7 +659,7 @@ def binary_omen_buy_outcome_tx(
|
|
597
659
|
api_keys=api_keys,
|
598
660
|
amount=amount,
|
599
661
|
market=market,
|
600
|
-
outcome=
|
662
|
+
outcome=get_bet_outcome(binary_outcome),
|
601
663
|
auto_deposit=auto_deposit,
|
602
664
|
web3=web3,
|
603
665
|
)
|
@@ -686,7 +748,7 @@ def binary_omen_sell_outcome_tx(
|
|
686
748
|
api_keys=api_keys,
|
687
749
|
amount=amount,
|
688
750
|
market=market,
|
689
|
-
outcome=
|
751
|
+
outcome=get_bet_outcome(binary_outcome),
|
690
752
|
auto_withdraw=auto_withdraw,
|
691
753
|
web3=web3,
|
692
754
|
)
|
@@ -769,6 +831,7 @@ def omen_create_market_tx(
|
|
769
831
|
web3=web3,
|
770
832
|
)
|
771
833
|
|
834
|
+
# In case of ERC4626, obtained (for example) sDai out of xDai could be lower than the `amount_wei`, so we need to handle it.
|
772
835
|
initial_funds_in_shares = collateral_token_contract.get_in_shares(
|
773
836
|
amount=initial_funds_wei, web3=web3
|
774
837
|
)
|