prediction-market-agent-tooling 0.52.1__py3-none-any.whl → 0.53.0__py3-none-any.whl
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/deploy/agent.py +2 -1
- prediction_market_agent_tooling/deploy/betting_strategy.py +56 -46
- prediction_market_agent_tooling/jobs/omen/omen_jobs.py +1 -1
- prediction_market_agent_tooling/markets/agent_market.py +19 -1
- prediction_market_agent_tooling/markets/manifold/manifold.py +8 -0
- prediction_market_agent_tooling/markets/market_fees.py +36 -0
- prediction_market_agent_tooling/markets/metaculus/metaculus.py +2 -0
- prediction_market_agent_tooling/markets/omen/omen.py +32 -14
- prediction_market_agent_tooling/markets/omen/omen_contracts.py +1 -1
- prediction_market_agent_tooling/markets/omen/omen_subgraph_handler.py +15 -1
- prediction_market_agent_tooling/markets/polymarket/polymarket.py +7 -0
- prediction_market_agent_tooling/tools/betting_strategies/kelly_criterion.py +8 -2
- prediction_market_agent_tooling/tools/betting_strategies/market_moving.py +12 -5
- prediction_market_agent_tooling/tools/langfuse_client_utils.py +4 -1
- prediction_market_agent_tooling/tools/tavily/tavily_models.py +84 -0
- prediction_market_agent_tooling/tools/{tavily_storage/tavily_storage.py → tavily/tavily_search.py} +28 -2
- prediction_market_agent_tooling/tools/{tavily_storage/tavily_models.py → tavily/tavily_storage.py} +10 -87
- prediction_market_agent_tooling/tools/utils.py +3 -6
- {prediction_market_agent_tooling-0.52.1.dist-info → prediction_market_agent_tooling-0.53.0.dist-info}/METADATA +1 -1
- {prediction_market_agent_tooling-0.52.1.dist-info → prediction_market_agent_tooling-0.53.0.dist-info}/RECORD +23 -21
- {prediction_market_agent_tooling-0.52.1.dist-info → prediction_market_agent_tooling-0.53.0.dist-info}/LICENSE +0 -0
- {prediction_market_agent_tooling-0.52.1.dist-info → prediction_market_agent_tooling-0.53.0.dist-info}/WHEEL +0 -0
- {prediction_market_agent_tooling-0.52.1.dist-info → prediction_market_agent_tooling-0.53.0.dist-info}/entry_points.txt +0 -0
@@ -297,6 +297,7 @@ class DeployableTraderAgent(DeployableAgent):
|
|
297
297
|
min_required_balance_to_operate: xDai | None = xdai_type(1)
|
298
298
|
min_balance_to_keep_in_native_currency: xDai | None = xdai_type(0.1)
|
299
299
|
allow_invalid_questions: bool = False
|
300
|
+
same_market_bet_interval: timedelta = timedelta(hours=24)
|
300
301
|
|
301
302
|
def __init__(
|
302
303
|
self,
|
@@ -394,7 +395,7 @@ class DeployableTraderAgent(DeployableAgent):
|
|
394
395
|
Subclasses can implement their own logic instead of this one, or on top of this one.
|
395
396
|
By default, it allows only markets where user didn't bet recently and it's a reasonable question.
|
396
397
|
"""
|
397
|
-
if self.have_bet_on_market_since(market, since=
|
398
|
+
if self.have_bet_on_market_since(market, since=self.same_market_bet_interval):
|
398
399
|
return False
|
399
400
|
|
400
401
|
# Manifold allows to bet only on markets with probability between 1 and 99.
|
@@ -4,7 +4,7 @@ from scipy.optimize import minimize_scalar
|
|
4
4
|
|
5
5
|
from prediction_market_agent_tooling.gtypes import xDai
|
6
6
|
from prediction_market_agent_tooling.loggers import logger
|
7
|
-
from prediction_market_agent_tooling.markets.agent_market import AgentMarket
|
7
|
+
from prediction_market_agent_tooling.markets.agent_market import AgentMarket, MarketFees
|
8
8
|
from prediction_market_agent_tooling.markets.data_models import (
|
9
9
|
Currency,
|
10
10
|
Position,
|
@@ -19,7 +19,6 @@ from prediction_market_agent_tooling.markets.omen.omen import (
|
|
19
19
|
)
|
20
20
|
from prediction_market_agent_tooling.tools.betting_strategies.kelly_criterion import (
|
21
21
|
get_kelly_bet_full,
|
22
|
-
get_kelly_bet_simplified,
|
23
22
|
)
|
24
23
|
from prediction_market_agent_tooling.tools.betting_strategies.utils import SimpleBet
|
25
24
|
from prediction_market_agent_tooling.tools.utils import check_not_none
|
@@ -48,6 +47,26 @@ class BettingStrategy(ABC):
|
|
48
47
|
"Cannot handle trades with currencies that deviate from market's currency"
|
49
48
|
)
|
50
49
|
|
50
|
+
@staticmethod
|
51
|
+
def assert_buy_trade_wont_be_guaranteed_loss(
|
52
|
+
market: AgentMarket, trades: list[Trade]
|
53
|
+
) -> None:
|
54
|
+
for trade in trades:
|
55
|
+
if trade.trade_type == TradeType.BUY:
|
56
|
+
outcome_tokens_to_get = market.get_buy_token_amount(
|
57
|
+
trade.amount, trade.outcome
|
58
|
+
)
|
59
|
+
|
60
|
+
if outcome_tokens_to_get.amount < trade.amount.amount:
|
61
|
+
raise RuntimeError(
|
62
|
+
f"Trade {trade=} would result in guaranteed loss by getting only {outcome_tokens_to_get=}."
|
63
|
+
)
|
64
|
+
|
65
|
+
@staticmethod
|
66
|
+
def check_trades(market: AgentMarket, trades: list[Trade]) -> None:
|
67
|
+
BettingStrategy.assert_trades_currency_match_markets(market, trades)
|
68
|
+
BettingStrategy.assert_buy_trade_wont_be_guaranteed_loss(market, trades)
|
69
|
+
|
51
70
|
def _build_rebalance_trades_from_positions(
|
52
71
|
self,
|
53
72
|
existing_position: Position | None,
|
@@ -96,7 +115,10 @@ class BettingStrategy(ABC):
|
|
96
115
|
|
97
116
|
# Sort inplace with SELL last
|
98
117
|
trades.sort(key=lambda t: t.trade_type == TradeType.SELL)
|
99
|
-
|
118
|
+
|
119
|
+
# Run some sanity checks to not place unreasonable bets.
|
120
|
+
BettingStrategy.check_trades(market, trades)
|
121
|
+
|
100
122
|
return trades
|
101
123
|
|
102
124
|
|
@@ -153,32 +175,24 @@ class KellyBettingStrategy(BettingStrategy):
|
|
153
175
|
market: AgentMarket,
|
154
176
|
) -> list[Trade]:
|
155
177
|
outcome_token_pool = check_not_none(market.outcome_token_pool)
|
156
|
-
kelly_bet = (
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
)
|
168
|
-
if market.has_token_pool()
|
169
|
-
else get_kelly_bet_simplified(
|
170
|
-
self.max_bet_amount,
|
171
|
-
market.current_p_yes,
|
172
|
-
answer.p_yes,
|
173
|
-
answer.confidence,
|
174
|
-
)
|
178
|
+
kelly_bet = get_kelly_bet_full(
|
179
|
+
yes_outcome_pool_size=outcome_token_pool[
|
180
|
+
market.get_outcome_str_from_bool(True)
|
181
|
+
],
|
182
|
+
no_outcome_pool_size=outcome_token_pool[
|
183
|
+
market.get_outcome_str_from_bool(False)
|
184
|
+
],
|
185
|
+
estimated_p_yes=answer.p_yes,
|
186
|
+
max_bet=self.max_bet_amount,
|
187
|
+
confidence=answer.confidence,
|
188
|
+
fees=market.fees,
|
175
189
|
)
|
176
190
|
|
177
191
|
kelly_bet_size = kelly_bet.size
|
178
192
|
if self.max_price_impact:
|
179
193
|
# Adjust amount
|
180
194
|
max_price_impact_bet_amount = self.calculate_bet_amount_for_price_impact(
|
181
|
-
market, kelly_bet
|
195
|
+
market, kelly_bet
|
182
196
|
)
|
183
197
|
|
184
198
|
# We just don't want Kelly size to extrapolate price_impact - hence we take the min.
|
@@ -196,7 +210,12 @@ class KellyBettingStrategy(BettingStrategy):
|
|
196
210
|
return trades
|
197
211
|
|
198
212
|
def calculate_price_impact_for_bet_amount(
|
199
|
-
self,
|
213
|
+
self,
|
214
|
+
buy_direction: bool,
|
215
|
+
bet_amount: float,
|
216
|
+
yes: float,
|
217
|
+
no: float,
|
218
|
+
fees: MarketFees,
|
200
219
|
) -> float:
|
201
220
|
total_outcome_tokens = yes + no
|
202
221
|
expected_price = (
|
@@ -204,7 +223,7 @@ class KellyBettingStrategy(BettingStrategy):
|
|
204
223
|
)
|
205
224
|
|
206
225
|
tokens_to_buy = get_buy_outcome_token_amount(
|
207
|
-
bet_amount, buy_direction, yes, no,
|
226
|
+
bet_amount, buy_direction, yes, no, fees
|
208
227
|
)
|
209
228
|
|
210
229
|
actual_price = bet_amount / tokens_to_buy
|
@@ -216,7 +235,6 @@ class KellyBettingStrategy(BettingStrategy):
|
|
216
235
|
self,
|
217
236
|
market: AgentMarket,
|
218
237
|
kelly_bet: SimpleBet,
|
219
|
-
fee: float,
|
220
238
|
) -> float:
|
221
239
|
def calculate_price_impact_deviation_from_target_price_impact(
|
222
240
|
bet_amount: xDai,
|
@@ -226,7 +244,7 @@ class KellyBettingStrategy(BettingStrategy):
|
|
226
244
|
bet_amount,
|
227
245
|
yes_outcome_pool_size,
|
228
246
|
no_outcome_pool_size,
|
229
|
-
|
247
|
+
market.fees,
|
230
248
|
)
|
231
249
|
# We return abs for the algorithm to converge to 0 instead of the min (and possibly negative) value.
|
232
250
|
|
@@ -285,25 +303,17 @@ class MaxAccuracyWithKellyScaledBetsStrategy(BettingStrategy):
|
|
285
303
|
estimated_p_yes = float(answer.p_yes > 0.5)
|
286
304
|
confidence = 1.0
|
287
305
|
|
288
|
-
kelly_bet = (
|
289
|
-
|
290
|
-
|
291
|
-
|
292
|
-
|
293
|
-
|
294
|
-
|
295
|
-
|
296
|
-
|
297
|
-
|
298
|
-
|
299
|
-
)
|
300
|
-
if market.has_token_pool()
|
301
|
-
else get_kelly_bet_simplified(
|
302
|
-
adjusted_bet_amount,
|
303
|
-
market.current_p_yes,
|
304
|
-
estimated_p_yes,
|
305
|
-
confidence,
|
306
|
-
)
|
306
|
+
kelly_bet = get_kelly_bet_full(
|
307
|
+
yes_outcome_pool_size=outcome_token_pool[
|
308
|
+
market.get_outcome_str_from_bool(True)
|
309
|
+
],
|
310
|
+
no_outcome_pool_size=outcome_token_pool[
|
311
|
+
market.get_outcome_str_from_bool(False)
|
312
|
+
],
|
313
|
+
estimated_p_yes=estimated_p_yes,
|
314
|
+
max_bet=adjusted_bet_amount,
|
315
|
+
confidence=confidence,
|
316
|
+
fees=market.fees,
|
307
317
|
)
|
308
318
|
|
309
319
|
amounts = {
|
@@ -75,7 +75,7 @@ class OmenJobAgentMarket(OmenAgentMarket, JobAgentMarket):
|
|
75
75
|
market_maker_contract_address_checksummed=market.market_maker_contract_address_checksummed,
|
76
76
|
condition=market.condition,
|
77
77
|
finalized_time=market.finalized_time,
|
78
|
-
|
78
|
+
fees=market.fees,
|
79
79
|
)
|
80
80
|
|
81
81
|
|
@@ -2,7 +2,7 @@ import typing as t
|
|
2
2
|
from enum import Enum
|
3
3
|
|
4
4
|
from eth_typing import ChecksumAddress
|
5
|
-
from pydantic import BaseModel, field_validator
|
5
|
+
from pydantic import BaseModel, field_validator, model_validator
|
6
6
|
from pydantic_core.core_schema import FieldValidationInfo
|
7
7
|
|
8
8
|
from prediction_market_agent_tooling.config import APIKeys
|
@@ -16,6 +16,7 @@ from prediction_market_agent_tooling.markets.data_models import (
|
|
16
16
|
ResolvedBet,
|
17
17
|
TokenAmount,
|
18
18
|
)
|
19
|
+
from prediction_market_agent_tooling.markets.market_fees import MarketFees
|
19
20
|
from prediction_market_agent_tooling.tools.utils import (
|
20
21
|
DatetimeUTC,
|
21
22
|
check_not_none,
|
@@ -60,6 +61,7 @@ class AgentMarket(BaseModel):
|
|
60
61
|
current_p_yes: Probability
|
61
62
|
url: str
|
62
63
|
volume: float | None # Should be in currency of `currency` above.
|
64
|
+
fees: MarketFees
|
63
65
|
|
64
66
|
@field_validator("outcome_token_pool")
|
65
67
|
def validate_outcome_token_pool(
|
@@ -77,6 +79,14 @@ class AgentMarket(BaseModel):
|
|
77
79
|
)
|
78
80
|
return outcome_token_pool
|
79
81
|
|
82
|
+
@model_validator(mode="before")
|
83
|
+
def handle_legacy_fee(cls, data: dict[str, t.Any]) -> dict[str, t.Any]:
|
84
|
+
# Backward compatibility for older `AgentMarket` without `fees`.
|
85
|
+
if "fees" not in data and "fee" in data:
|
86
|
+
data["fees"] = MarketFees(absolute=0.0, bet_proportion=data["fee"])
|
87
|
+
del data["fee"]
|
88
|
+
return data
|
89
|
+
|
80
90
|
@property
|
81
91
|
def current_p_no(self) -> Probability:
|
82
92
|
return Probability(1 - self.current_p_yes)
|
@@ -166,6 +176,11 @@ class AgentMarket(BaseModel):
|
|
166
176
|
def buy_tokens(self, outcome: bool, amount: TokenAmount) -> str:
|
167
177
|
return self.place_bet(outcome=outcome, amount=amount)
|
168
178
|
|
179
|
+
def get_buy_token_amount(
|
180
|
+
self, bet_amount: BetAmount, direction: bool
|
181
|
+
) -> TokenAmount:
|
182
|
+
raise NotImplementedError("Subclasses must implement this method")
|
183
|
+
|
169
184
|
def sell_tokens(self, outcome: bool, amount: TokenAmount) -> str:
|
170
185
|
raise NotImplementedError("Subclasses must implement this method")
|
171
186
|
|
@@ -285,3 +300,6 @@ class AgentMarket(BaseModel):
|
|
285
300
|
@staticmethod
|
286
301
|
def get_user_id(api_keys: APIKeys) -> str:
|
287
302
|
raise NotImplementedError("Subclasses must implement this method")
|
303
|
+
|
304
|
+
def get_most_recent_trade_datetime(self, user_id: str) -> DatetimeUTC | None:
|
305
|
+
raise NotImplementedError("Subclasses must implement this method")
|
@@ -6,6 +6,7 @@ from prediction_market_agent_tooling.gtypes import Mana, Probability, mana_type
|
|
6
6
|
from prediction_market_agent_tooling.markets.agent_market import (
|
7
7
|
AgentMarket,
|
8
8
|
FilterBy,
|
9
|
+
MarketFees,
|
9
10
|
SortBy,
|
10
11
|
)
|
11
12
|
from prediction_market_agent_tooling.markets.data_models import BetAmount, Currency
|
@@ -33,6 +34,13 @@ class ManifoldAgentMarket(AgentMarket):
|
|
33
34
|
currency: t.ClassVar[Currency] = Currency.Mana
|
34
35
|
base_url: t.ClassVar[str] = MANIFOLD_BASE_URL
|
35
36
|
|
37
|
+
# 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.
|
38
|
+
# So we just consider them as 0, which anyway is true for all markets I randomly checked on Manifold.
|
39
|
+
fees: MarketFees = MarketFees(
|
40
|
+
bet_proportion=0,
|
41
|
+
absolute=0.25, # For doing trades via API.
|
42
|
+
)
|
43
|
+
|
36
44
|
def get_last_trade_p_yes(self) -> Probability:
|
37
45
|
"""On Manifold, probablities aren't updated after the closure, so we can just use the current probability"""
|
38
46
|
return self.current_p_yes
|
@@ -0,0 +1,36 @@
|
|
1
|
+
from pydantic import BaseModel, Field
|
2
|
+
|
3
|
+
|
4
|
+
class MarketFees(BaseModel):
|
5
|
+
bet_proportion: float = Field(
|
6
|
+
..., ge=0.0, lt=1.0
|
7
|
+
) # proportion of the bet, from 0 to 1
|
8
|
+
absolute: float # absolute value paid in the currency of the market
|
9
|
+
|
10
|
+
@staticmethod
|
11
|
+
def get_zero_fees(
|
12
|
+
bet_proportion: float = 0.0,
|
13
|
+
absolute: float = 0.0,
|
14
|
+
) -> "MarketFees":
|
15
|
+
return MarketFees(
|
16
|
+
bet_proportion=bet_proportion,
|
17
|
+
absolute=absolute,
|
18
|
+
)
|
19
|
+
|
20
|
+
def total_fee_absolute_value(self, bet_amount: float) -> float:
|
21
|
+
"""
|
22
|
+
Returns the total fee in absolute terms, including both proportional and fixed fees.
|
23
|
+
"""
|
24
|
+
return self.bet_proportion * bet_amount + self.absolute
|
25
|
+
|
26
|
+
def total_fee_relative_value(self, bet_amount: float) -> float:
|
27
|
+
"""
|
28
|
+
Returns the total fee relative to the bet amount, including both proportional and fixed fees.
|
29
|
+
"""
|
30
|
+
if bet_amount == 0:
|
31
|
+
return 0.0
|
32
|
+
total_fee = self.total_fee_absolute_value(bet_amount)
|
33
|
+
return total_fee / bet_amount
|
34
|
+
|
35
|
+
def get_bet_size_after_fees(self, bet_amount: float) -> float:
|
36
|
+
return bet_amount - self.total_fee_absolute_value(bet_amount)
|
@@ -5,6 +5,7 @@ from prediction_market_agent_tooling.gtypes import Probability
|
|
5
5
|
from prediction_market_agent_tooling.markets.agent_market import (
|
6
6
|
AgentMarket,
|
7
7
|
FilterBy,
|
8
|
+
MarketFees,
|
8
9
|
SortBy,
|
9
10
|
)
|
10
11
|
from prediction_market_agent_tooling.markets.metaculus.api import (
|
@@ -29,6 +30,7 @@ class MetaculusAgentMarket(AgentMarket):
|
|
29
30
|
description: str | None = (
|
30
31
|
None # Metaculus markets don't have a description, so just default to None.
|
31
32
|
)
|
33
|
+
fees: MarketFees = MarketFees.get_zero_fees() # No fees on Metaculus.
|
32
34
|
|
33
35
|
@staticmethod
|
34
36
|
def from_data_model(model: MetaculusQuestion) -> "MetaculusAgentMarket":
|
@@ -22,6 +22,7 @@ from prediction_market_agent_tooling.loggers import logger
|
|
22
22
|
from prediction_market_agent_tooling.markets.agent_market import (
|
23
23
|
AgentMarket,
|
24
24
|
FilterBy,
|
25
|
+
MarketFees,
|
25
26
|
SortBy,
|
26
27
|
)
|
27
28
|
from prediction_market_agent_tooling.markets.data_models import (
|
@@ -101,7 +102,6 @@ class OmenAgentMarket(AgentMarket):
|
|
101
102
|
finalized_time: DatetimeUTC | None
|
102
103
|
created_time: DatetimeUTC
|
103
104
|
close_time: DatetimeUTC
|
104
|
-
fee: float # proportion, from 0 to 1
|
105
105
|
|
106
106
|
_binary_market_p_yes_history: list[Probability] | None = None
|
107
107
|
description: str | None = (
|
@@ -138,11 +138,11 @@ class OmenAgentMarket(AgentMarket):
|
|
138
138
|
else None
|
139
139
|
)
|
140
140
|
|
141
|
-
def get_liquidity_in_wei(self) -> Wei:
|
142
|
-
return self.get_contract().totalSupply()
|
141
|
+
def get_liquidity_in_wei(self, web3: Web3 | None = None) -> Wei:
|
142
|
+
return self.get_contract().totalSupply(web3)
|
143
143
|
|
144
|
-
def get_liquidity_in_xdai(self) -> xDai:
|
145
|
-
return wei_to_xdai(self.get_liquidity_in_wei())
|
144
|
+
def get_liquidity_in_xdai(self, web3: Web3 | None = None) -> xDai:
|
145
|
+
return wei_to_xdai(self.get_liquidity_in_wei(web3))
|
146
146
|
|
147
147
|
def get_liquidity(self) -> TokenAmount:
|
148
148
|
return TokenAmount(
|
@@ -186,6 +186,7 @@ class OmenAgentMarket(AgentMarket):
|
|
186
186
|
amount=token_amount,
|
187
187
|
auto_withdraw=False,
|
188
188
|
web3=web3,
|
189
|
+
api_keys=api_keys,
|
189
190
|
)
|
190
191
|
|
191
192
|
def place_bet(
|
@@ -240,7 +241,7 @@ class OmenAgentMarket(AgentMarket):
|
|
240
241
|
shares_to_sell=amount.amount,
|
241
242
|
holdings=wei_to_xdai(pool_balance[self.get_index_set(sell_str)]),
|
242
243
|
other_holdings=wei_to_xdai(pool_balance[self.get_index_set(other_str)]),
|
243
|
-
|
244
|
+
fees=self.fees,
|
244
245
|
)
|
245
246
|
return xDai(collateral)
|
246
247
|
|
@@ -352,7 +353,12 @@ class OmenAgentMarket(AgentMarket):
|
|
352
353
|
url=model.url,
|
353
354
|
volume=wei_to_xdai(model.collateralVolume),
|
354
355
|
close_time=model.close_time,
|
355
|
-
|
356
|
+
fees=MarketFees(
|
357
|
+
bet_proportion=(
|
358
|
+
float(wei_to_xdai(model.fee)) if model.fee is not None else 0.0
|
359
|
+
),
|
360
|
+
absolute=0,
|
361
|
+
),
|
356
362
|
outcome_token_pool={
|
357
363
|
model.outcomes[i]: wei_to_xdai(Wei(model.outcomeTokenAmounts[i]))
|
358
364
|
for i in range(len(model.outcomes))
|
@@ -598,7 +604,7 @@ class OmenAgentMarket(AgentMarket):
|
|
598
604
|
buy_direction=direction,
|
599
605
|
yes_outcome_pool_size=outcome_token_pool[OMEN_TRUE_OUTCOME],
|
600
606
|
no_outcome_pool_size=outcome_token_pool[OMEN_FALSE_OUTCOME],
|
601
|
-
|
607
|
+
fees=self.fees,
|
602
608
|
)
|
603
609
|
return TokenAmount(amount=amount, currency=self.currency)
|
604
610
|
|
@@ -628,10 +634,10 @@ class OmenAgentMarket(AgentMarket):
|
|
628
634
|
no_outcome_pool_size = outcome_token_pool[self.get_outcome_str_from_bool(False)]
|
629
635
|
|
630
636
|
new_yes_outcome_pool_size = yes_outcome_pool_size + (
|
631
|
-
bet_amount.amount
|
637
|
+
self.fees.get_bet_size_after_fees(bet_amount.amount)
|
632
638
|
)
|
633
639
|
new_no_outcome_pool_size = no_outcome_pool_size + (
|
634
|
-
bet_amount.amount
|
640
|
+
self.fees.get_bet_size_after_fees(bet_amount.amount)
|
635
641
|
)
|
636
642
|
|
637
643
|
received_token_amount = self.get_buy_token_amount(bet_amount, direction).amount
|
@@ -653,6 +659,20 @@ class OmenAgentMarket(AgentMarket):
|
|
653
659
|
def get_user_id(api_keys: APIKeys) -> str:
|
654
660
|
return api_keys.bet_from_address
|
655
661
|
|
662
|
+
def get_most_recent_trade_datetime(self, user_id: str) -> DatetimeUTC | None:
|
663
|
+
sgh = OmenSubgraphHandler()
|
664
|
+
trades = sgh.get_trades(
|
665
|
+
sort_by_field=sgh.trades_subgraph.FpmmTrade.creationTimestamp,
|
666
|
+
sort_direction="desc",
|
667
|
+
limit=1,
|
668
|
+
better_address=Web3.to_checksum_address(user_id),
|
669
|
+
market_id=Web3.to_checksum_address(self.id),
|
670
|
+
)
|
671
|
+
if not trades:
|
672
|
+
return None
|
673
|
+
|
674
|
+
return trades[0].creation_datetime
|
675
|
+
|
656
676
|
|
657
677
|
def get_omen_user_url(address: ChecksumAddress) -> str:
|
658
678
|
return f"https://gnosisscan.io/address/{address}"
|
@@ -1104,7 +1124,6 @@ def omen_remove_fund_market_tx(
|
|
1104
1124
|
market_contract.removeFunding(api_keys=api_keys, remove_funding=shares, web3=web3)
|
1105
1125
|
|
1106
1126
|
conditional_tokens = OmenConditionalTokenContract()
|
1107
|
-
parent_collection_id = build_parent_collection_id()
|
1108
1127
|
amount_per_index_set = get_conditional_tokens_balance_for_market(
|
1109
1128
|
market, from_address, web3
|
1110
1129
|
)
|
@@ -1116,7 +1135,6 @@ def omen_remove_fund_market_tx(
|
|
1116
1135
|
result = conditional_tokens.mergePositions(
|
1117
1136
|
api_keys=api_keys,
|
1118
1137
|
collateral_token_address=market.collateral_token_contract_address_checksummed,
|
1119
|
-
parent_collection_id=parent_collection_id,
|
1120
1138
|
conditionId=market.condition.id,
|
1121
1139
|
index_sets=market.condition.index_sets,
|
1122
1140
|
amount=amount_to_merge,
|
@@ -1266,14 +1284,14 @@ def get_buy_outcome_token_amount(
|
|
1266
1284
|
buy_direction: bool,
|
1267
1285
|
yes_outcome_pool_size: float,
|
1268
1286
|
no_outcome_pool_size: float,
|
1269
|
-
|
1287
|
+
fees: MarketFees,
|
1270
1288
|
) -> float:
|
1271
1289
|
"""
|
1272
1290
|
Calculates the amount of outcome tokens received for a given investment
|
1273
1291
|
|
1274
1292
|
Taken from https://github.com/gnosis/conditional-tokens-market-makers/blob/6814c0247c745680bb13298d4f0dd7f5b574d0db/contracts/FixedProductMarketMaker.sol#L264
|
1275
1293
|
"""
|
1276
|
-
investment_amount_minus_fees = investment_amount
|
1294
|
+
investment_amount_minus_fees = fees.get_bet_size_after_fees(investment_amount)
|
1277
1295
|
buy_token_pool_balance = (
|
1278
1296
|
yes_outcome_pool_size if buy_direction else no_outcome_pool_size
|
1279
1297
|
)
|
@@ -163,10 +163,10 @@ class OmenConditionalTokenContract(ContractOnGnosisChain):
|
|
163
163
|
self,
|
164
164
|
api_keys: APIKeys,
|
165
165
|
collateral_token_address: ChecksumAddress,
|
166
|
-
parent_collection_id: HexStr,
|
167
166
|
conditionId: HexBytes,
|
168
167
|
index_sets: t.List[int],
|
169
168
|
amount: Wei,
|
169
|
+
parent_collection_id: HexStr = build_parent_collection_id(),
|
170
170
|
web3: Web3 | None = None,
|
171
171
|
) -> TxReceipt:
|
172
172
|
return self.send(
|
@@ -340,6 +340,7 @@ class OmenSubgraphHandler(metaclass=SingletonMeta):
|
|
340
340
|
tuple[ChecksumAddress, ...] | None
|
341
341
|
) = SAFE_COLLATERAL_TOKEN_MARKETS,
|
342
342
|
category: str | None = None,
|
343
|
+
creator_in: t.Sequence[HexAddress] | None = None,
|
343
344
|
) -> t.List[OmenMarket]:
|
344
345
|
"""
|
345
346
|
Simplified `get_omen_binary_markets` method, which allows to fetch markets based on the filter_by and sort_by values.
|
@@ -375,6 +376,7 @@ class OmenSubgraphHandler(metaclass=SingletonMeta):
|
|
375
376
|
question_excluded_titles=excluded_questions,
|
376
377
|
collateral_token_address_in=collateral_token_address_in,
|
377
378
|
category=category,
|
379
|
+
creator_in=creator_in,
|
378
380
|
)
|
379
381
|
|
380
382
|
def get_omen_binary_markets(
|
@@ -546,6 +548,7 @@ class OmenSubgraphHandler(metaclass=SingletonMeta):
|
|
546
548
|
|
547
549
|
def get_trades(
|
548
550
|
self,
|
551
|
+
limit: int | None = None,
|
549
552
|
better_address: ChecksumAddress | None = None,
|
550
553
|
start_time: DatetimeUTC | None = None,
|
551
554
|
end_time: t.Optional[DatetimeUTC] = None,
|
@@ -554,6 +557,8 @@ class OmenSubgraphHandler(metaclass=SingletonMeta):
|
|
554
557
|
type_: t.Literal["Buy", "Sell"] | None = None,
|
555
558
|
market_opening_after: DatetimeUTC | None = None,
|
556
559
|
collateral_amount_more_than: Wei | None = None,
|
560
|
+
sort_by_field: FieldPath | None = None,
|
561
|
+
sort_direction: str | None = None,
|
557
562
|
) -> list[OmenBet]:
|
558
563
|
if not end_time:
|
559
564
|
end_time = utcnow()
|
@@ -579,8 +584,17 @@ class OmenSubgraphHandler(metaclass=SingletonMeta):
|
|
579
584
|
if collateral_amount_more_than is not None:
|
580
585
|
where_stms.append(trade.collateralAmount > collateral_amount_more_than)
|
581
586
|
|
587
|
+
# These values can not be set to `None`, but they can be omitted.
|
588
|
+
optional_params = {}
|
589
|
+
if sort_by_field is not None:
|
590
|
+
optional_params["orderBy"] = sort_by_field
|
591
|
+
if sort_direction is not None:
|
592
|
+
optional_params["orderDirection"] = sort_direction
|
593
|
+
|
582
594
|
trades = self.trades_subgraph.Query.fpmmTrades(
|
583
|
-
first=sys.maxsize,
|
595
|
+
first=limit if limit else sys.maxsize,
|
596
|
+
where=where_stms,
|
597
|
+
**optional_params,
|
584
598
|
)
|
585
599
|
fields = self._get_fields_for_bets(trades)
|
586
600
|
result = self.sg.query_json(fields)
|
@@ -3,6 +3,7 @@ import typing as t
|
|
3
3
|
from prediction_market_agent_tooling.markets.agent_market import (
|
4
4
|
AgentMarket,
|
5
5
|
FilterBy,
|
6
|
+
MarketFees,
|
6
7
|
SortBy,
|
7
8
|
)
|
8
9
|
from prediction_market_agent_tooling.markets.data_models import BetAmount, Currency
|
@@ -26,6 +27,12 @@ class PolymarketAgentMarket(AgentMarket):
|
|
26
27
|
currency: t.ClassVar[Currency] = Currency.USDC
|
27
28
|
base_url: t.ClassVar[str] = POLYMARKET_BASE_URL
|
28
29
|
|
30
|
+
# Based on https://docs.polymarket.com/#fees, there are currently no fees, except for transactions fees.
|
31
|
+
# However they do have `maker_fee_base_rate` and `taker_fee_base_rate`, but impossible to test out our implementation without them actually taking the fees.
|
32
|
+
# But then in the new subgraph API, they have `fee: BigInt! (Percentage fee of trades taken by market maker. A 2% fee is represented as 2*10^16)`.
|
33
|
+
# TODO: Check out the fees while integrating the subgraph API or if we implement placing of bets on Polymarket.
|
34
|
+
fees: MarketFees = MarketFees.get_zero_fees()
|
35
|
+
|
29
36
|
@staticmethod
|
30
37
|
def from_data_model(model: PolymarketMarketWithPrices) -> "PolymarketAgentMarket":
|
31
38
|
return PolymarketAgentMarket(
|
@@ -1,3 +1,4 @@
|
|
1
|
+
from prediction_market_agent_tooling.markets.market_fees import MarketFees
|
1
2
|
from prediction_market_agent_tooling.tools.betting_strategies.utils import SimpleBet
|
2
3
|
|
3
4
|
|
@@ -61,7 +62,7 @@ def get_kelly_bet_full(
|
|
61
62
|
estimated_p_yes: float,
|
62
63
|
confidence: float,
|
63
64
|
max_bet: float,
|
64
|
-
|
65
|
+
fees: MarketFees,
|
65
66
|
) -> SimpleBet:
|
66
67
|
"""
|
67
68
|
Calculate the optimal bet amount using the Kelly Criterion for a binary outcome market.
|
@@ -86,9 +87,14 @@ def get_kelly_bet_full(
|
|
86
87
|
limitations under the License.
|
87
88
|
```
|
88
89
|
"""
|
90
|
+
fee = fees.bet_proportion
|
91
|
+
if fees.absolute > 0:
|
92
|
+
raise RuntimeError(
|
93
|
+
f"Kelly works only with bet-proportional fees, but the fees are {fees=}."
|
94
|
+
)
|
95
|
+
|
89
96
|
check_is_valid_probability(estimated_p_yes)
|
90
97
|
check_is_valid_probability(confidence)
|
91
|
-
check_is_valid_probability(fee)
|
92
98
|
|
93
99
|
if max_bet == 0:
|
94
100
|
return SimpleBet(direction=True, size=0)
|
@@ -3,7 +3,10 @@ from functools import reduce
|
|
3
3
|
import numpy as np
|
4
4
|
|
5
5
|
from prediction_market_agent_tooling.gtypes import Probability, Wei, xDai
|
6
|
-
from prediction_market_agent_tooling.markets.omen.omen import
|
6
|
+
from prediction_market_agent_tooling.markets.omen.omen import (
|
7
|
+
MarketFees,
|
8
|
+
OmenAgentMarket,
|
9
|
+
)
|
7
10
|
from prediction_market_agent_tooling.tools.betting_strategies.utils import SimpleBet
|
8
11
|
from prediction_market_agent_tooling.tools.utils import check_not_none
|
9
12
|
from prediction_market_agent_tooling.tools.web3_utils import wei_to_xdai, xdai_to_wei
|
@@ -14,7 +17,7 @@ def get_market_moving_bet(
|
|
14
17
|
no_outcome_pool_size: float,
|
15
18
|
market_p_yes: float,
|
16
19
|
target_p_yes: float,
|
17
|
-
|
20
|
+
fees: MarketFees,
|
18
21
|
max_iters: int = 100,
|
19
22
|
) -> SimpleBet:
|
20
23
|
"""
|
@@ -47,7 +50,7 @@ def get_market_moving_bet(
|
|
47
50
|
# Binary search for the optimal bet amount
|
48
51
|
for _ in range(max_iters):
|
49
52
|
bet_amount = (min_bet_amount + max_bet_amount) / 2
|
50
|
-
amounts_diff = bet_amount
|
53
|
+
amounts_diff = fees.get_bet_size_after_fees(bet_amount)
|
51
54
|
|
52
55
|
# Initial new amounts are old amounts + equal new amounts for each outcome
|
53
56
|
yes_outcome_new_pool_size = yes_outcome_pool_size + amounts_diff
|
@@ -106,16 +109,20 @@ def _sanity_check_omen_market_moving_bet(
|
|
106
109
|
no_outcome_pool_size = outcome_token_pool[market.get_outcome_str_from_bool(False)]
|
107
110
|
market_const = yes_outcome_pool_size * no_outcome_pool_size
|
108
111
|
|
112
|
+
bet_to_check_size_after_fees = market.fees.get_bet_size_after_fees(
|
113
|
+
bet_to_check.size
|
114
|
+
)
|
115
|
+
|
109
116
|
# When you buy 'yes' tokens, you add your bet size to the both pools, then
|
110
117
|
# subtract `buy_amount` from the 'yes' pool. And vice versa for 'no' tokens.
|
111
118
|
new_yes_outcome_pool_size = (
|
112
119
|
yes_outcome_pool_size
|
113
|
-
+
|
120
|
+
+ bet_to_check_size_after_fees
|
114
121
|
- float(bet_to_check.direction) * buy_amount
|
115
122
|
)
|
116
123
|
new_no_outcome_pool_size = (
|
117
124
|
no_outcome_pool_size
|
118
|
-
+
|
125
|
+
+ bet_to_check_size_after_fees
|
119
126
|
- float(not bet_to_check.direction) * buy_amount
|
120
127
|
)
|
121
128
|
new_market_const = new_yes_outcome_pool_size * new_no_outcome_pool_size
|
@@ -93,15 +93,18 @@ def get_traces_for_agent(
|
|
93
93
|
|
94
94
|
def trace_to_omen_agent_market(trace: TraceWithDetails) -> OmenAgentMarket | None:
|
95
95
|
if not trace.input:
|
96
|
+
logger.warning(f"No input in the trace: {trace}")
|
96
97
|
return None
|
97
98
|
if not trace.input["args"]:
|
99
|
+
logger.warning(f"No args in the trace: {trace}")
|
98
100
|
return None
|
99
101
|
assert len(trace.input["args"]) == 2 and trace.input["args"][0] == "omen"
|
100
102
|
try:
|
101
103
|
# If the market model is invalid (e.g. outdated), it will raise an exception
|
102
104
|
market = OmenAgentMarket.model_validate(trace.input["args"][1])
|
103
105
|
return market
|
104
|
-
except Exception:
|
106
|
+
except Exception as e:
|
107
|
+
logger.warning(f"Market not parsed from langfuse because: {e}")
|
105
108
|
return None
|
106
109
|
|
107
110
|
|
@@ -0,0 +1,84 @@
|
|
1
|
+
import typing as t
|
2
|
+
|
3
|
+
from pydantic import BaseModel
|
4
|
+
from sqlalchemy import Column
|
5
|
+
from sqlalchemy.dialects.postgresql import JSONB
|
6
|
+
from sqlmodel import ARRAY, Field, SQLModel, String
|
7
|
+
|
8
|
+
from prediction_market_agent_tooling.tools.utils import DatetimeUTC, utcnow
|
9
|
+
|
10
|
+
|
11
|
+
class TavilyResult(BaseModel):
|
12
|
+
title: str
|
13
|
+
url: str
|
14
|
+
content: str
|
15
|
+
score: float
|
16
|
+
raw_content: str | None
|
17
|
+
|
18
|
+
|
19
|
+
class TavilyResponse(BaseModel):
|
20
|
+
query: str
|
21
|
+
follow_up_questions: str | None = None
|
22
|
+
answer: str
|
23
|
+
images: list[str]
|
24
|
+
results: list[TavilyResult]
|
25
|
+
response_time: float
|
26
|
+
|
27
|
+
|
28
|
+
class TavilyResponseModel(SQLModel, table=True):
|
29
|
+
__tablename__ = "tavily_response"
|
30
|
+
__table_args__ = {"extend_existing": True}
|
31
|
+
id: int | None = Field(None, primary_key=True)
|
32
|
+
agent_id: str = Field(index=True, nullable=False)
|
33
|
+
# Parameters used to execute the search
|
34
|
+
query: str = Field(index=True, nullable=False)
|
35
|
+
search_depth: str
|
36
|
+
topic: str
|
37
|
+
days: int | None = Field(default=None, nullable=True)
|
38
|
+
max_results: int
|
39
|
+
include_domains: list[str] | None = Field(
|
40
|
+
None, sa_column=Column(ARRAY(String), nullable=True)
|
41
|
+
)
|
42
|
+
exclude_domains: list[str] | None = Field(
|
43
|
+
None, sa_column=Column(ARRAY(String), nullable=True)
|
44
|
+
)
|
45
|
+
include_answer: bool
|
46
|
+
include_raw_content: bool
|
47
|
+
include_images: bool
|
48
|
+
use_cache: bool
|
49
|
+
# Datetime at the time of search response and response from the search
|
50
|
+
datetime_: DatetimeUTC = Field(index=True, nullable=False)
|
51
|
+
response: dict[str, t.Any] = Field(sa_column=Column(JSONB, nullable=False))
|
52
|
+
|
53
|
+
@staticmethod
|
54
|
+
def from_model(
|
55
|
+
agent_id: str,
|
56
|
+
query: str,
|
57
|
+
search_depth: t.Literal["basic", "advanced"],
|
58
|
+
topic: t.Literal["general", "news"],
|
59
|
+
days: int | None,
|
60
|
+
max_results: int,
|
61
|
+
include_domains: t.Sequence[str] | None,
|
62
|
+
exclude_domains: t.Sequence[str] | None,
|
63
|
+
include_answer: bool,
|
64
|
+
include_raw_content: bool,
|
65
|
+
include_images: bool,
|
66
|
+
use_cache: bool,
|
67
|
+
response: TavilyResponse,
|
68
|
+
) -> "TavilyResponseModel":
|
69
|
+
return TavilyResponseModel(
|
70
|
+
agent_id=agent_id,
|
71
|
+
query=query,
|
72
|
+
search_depth=search_depth,
|
73
|
+
topic=topic,
|
74
|
+
days=days,
|
75
|
+
max_results=max_results,
|
76
|
+
include_domains=sorted(include_domains) if include_domains else None,
|
77
|
+
exclude_domains=sorted(exclude_domains) if exclude_domains else None,
|
78
|
+
include_answer=include_answer,
|
79
|
+
include_raw_content=include_raw_content,
|
80
|
+
include_images=include_images,
|
81
|
+
use_cache=use_cache,
|
82
|
+
datetime_=utcnow(),
|
83
|
+
response=response.model_dump(),
|
84
|
+
)
|
prediction_market_agent_tooling/tools/{tavily_storage/tavily_storage.py → tavily/tavily_search.py}
RENAMED
@@ -4,16 +4,20 @@ import tenacity
|
|
4
4
|
from tavily import TavilyClient
|
5
5
|
|
6
6
|
from prediction_market_agent_tooling.config import APIKeys
|
7
|
-
from prediction_market_agent_tooling.tools.
|
7
|
+
from prediction_market_agent_tooling.tools.tavily.tavily_models import (
|
8
8
|
TavilyResponse,
|
9
|
-
|
9
|
+
TavilyResult,
|
10
10
|
)
|
11
|
+
from prediction_market_agent_tooling.tools.tavily.tavily_storage import TavilyStorage
|
12
|
+
|
13
|
+
DEFAULT_SCORE_THRESHOLD = 0.75 # Based on some empirical testing, anything lower wasn't very relevant to the question being asked
|
11
14
|
|
12
15
|
|
13
16
|
def tavily_search(
|
14
17
|
query: str,
|
15
18
|
search_depth: t.Literal["basic", "advanced"] = "advanced",
|
16
19
|
topic: t.Literal["general", "news"] = "general",
|
20
|
+
days: int | None = None,
|
17
21
|
max_results: int = 5,
|
18
22
|
include_domains: t.Sequence[str] | None = None,
|
19
23
|
exclude_domains: t.Sequence[str] | None = None,
|
@@ -35,6 +39,7 @@ def tavily_search(
|
|
35
39
|
search_depth=search_depth,
|
36
40
|
topic=topic,
|
37
41
|
max_results=max_results,
|
42
|
+
days=days,
|
38
43
|
include_domains=include_domains,
|
39
44
|
exclude_domains=exclude_domains,
|
40
45
|
include_answer=include_answer,
|
@@ -49,6 +54,7 @@ def tavily_search(
|
|
49
54
|
search_depth=search_depth,
|
50
55
|
topic=topic,
|
51
56
|
max_results=max_results,
|
57
|
+
days=days,
|
52
58
|
include_domains=include_domains,
|
53
59
|
exclude_domains=exclude_domains,
|
54
60
|
include_answer=include_answer,
|
@@ -63,6 +69,7 @@ def tavily_search(
|
|
63
69
|
query=query,
|
64
70
|
search_depth=search_depth,
|
65
71
|
topic=topic,
|
72
|
+
days=days,
|
66
73
|
max_results=max_results,
|
67
74
|
include_domains=include_domains,
|
68
75
|
exclude_domains=exclude_domains,
|
@@ -80,6 +87,7 @@ def _tavily_search(
|
|
80
87
|
query: str,
|
81
88
|
search_depth: t.Literal["basic", "advanced"],
|
82
89
|
topic: t.Literal["general", "news"],
|
90
|
+
days: int | None,
|
83
91
|
max_results: int,
|
84
92
|
include_domains: t.Sequence[str] | None,
|
85
93
|
exclude_domains: t.Sequence[str] | None,
|
@@ -99,6 +107,7 @@ def _tavily_search(
|
|
99
107
|
query=query,
|
100
108
|
search_depth=search_depth,
|
101
109
|
topic=topic,
|
110
|
+
days=days,
|
102
111
|
max_results=max_results,
|
103
112
|
include_domains=include_domains,
|
104
113
|
exclude_domains=exclude_domains,
|
@@ -108,3 +117,20 @@ def _tavily_search(
|
|
108
117
|
use_cache=use_cache,
|
109
118
|
)
|
110
119
|
return response
|
120
|
+
|
121
|
+
|
122
|
+
def get_related_news_since(
|
123
|
+
question: str,
|
124
|
+
days_ago: int,
|
125
|
+
score_threshold: float = DEFAULT_SCORE_THRESHOLD,
|
126
|
+
max_results: int = 3,
|
127
|
+
tavily_storage: TavilyStorage | None = None,
|
128
|
+
) -> list[TavilyResult]:
|
129
|
+
news = tavily_search(
|
130
|
+
query=question,
|
131
|
+
days=days_ago,
|
132
|
+
max_results=max_results,
|
133
|
+
topic="news",
|
134
|
+
tavily_storage=tavily_storage,
|
135
|
+
)
|
136
|
+
return [r for r in news.results if r.score > score_threshold]
|
prediction_market_agent_tooling/tools/{tavily_storage/tavily_models.py → tavily/tavily_storage.py}
RENAMED
@@ -2,96 +2,15 @@ import typing as t
|
|
2
2
|
from datetime import timedelta
|
3
3
|
|
4
4
|
import tenacity
|
5
|
-
from
|
6
|
-
from sqlalchemy import Column
|
7
|
-
from sqlalchemy.dialects.postgresql import JSONB
|
8
|
-
from sqlmodel import (
|
9
|
-
ARRAY,
|
10
|
-
Field,
|
11
|
-
Session,
|
12
|
-
SQLModel,
|
13
|
-
String,
|
14
|
-
create_engine,
|
15
|
-
desc,
|
16
|
-
select,
|
17
|
-
)
|
5
|
+
from sqlmodel import Session, SQLModel, create_engine, desc, select
|
18
6
|
|
19
7
|
from prediction_market_agent_tooling.config import APIKeys
|
20
8
|
from prediction_market_agent_tooling.loggers import logger
|
21
|
-
from prediction_market_agent_tooling.tools.
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
url: str
|
27
|
-
content: str
|
28
|
-
score: float
|
29
|
-
raw_content: str | None
|
30
|
-
|
31
|
-
|
32
|
-
class TavilyResponse(BaseModel):
|
33
|
-
query: str
|
34
|
-
follow_up_questions: None = None
|
35
|
-
answer: str
|
36
|
-
images: list[str]
|
37
|
-
results: list[TavilyResult]
|
38
|
-
response_time: float
|
39
|
-
|
40
|
-
|
41
|
-
class TavilyResponseModel(SQLModel, table=True):
|
42
|
-
__tablename__ = "tavily_response"
|
43
|
-
__table_args__ = {"extend_existing": True}
|
44
|
-
id: int | None = Field(None, primary_key=True)
|
45
|
-
agent_id: str = Field(index=True, nullable=False)
|
46
|
-
# Parameters used to execute the search
|
47
|
-
query: str = Field(index=True, nullable=False)
|
48
|
-
search_depth: str
|
49
|
-
topic: str
|
50
|
-
max_results: int
|
51
|
-
include_domains: list[str] | None = Field(
|
52
|
-
None, sa_column=Column(ARRAY(String), nullable=True)
|
53
|
-
)
|
54
|
-
exclude_domains: list[str] | None = Field(
|
55
|
-
None, sa_column=Column(ARRAY(String), nullable=True)
|
56
|
-
)
|
57
|
-
include_answer: bool
|
58
|
-
include_raw_content: bool
|
59
|
-
include_images: bool
|
60
|
-
use_cache: bool
|
61
|
-
# Datetime at the time of search response and response from the search
|
62
|
-
datetime_: DatetimeUTC = Field(index=True, nullable=False)
|
63
|
-
response: dict[str, t.Any] = Field(sa_column=Column(JSONB, nullable=False))
|
64
|
-
|
65
|
-
@staticmethod
|
66
|
-
def from_model(
|
67
|
-
agent_id: str,
|
68
|
-
query: str,
|
69
|
-
search_depth: t.Literal["basic", "advanced"],
|
70
|
-
topic: t.Literal["general", "news"],
|
71
|
-
max_results: int,
|
72
|
-
include_domains: t.Sequence[str] | None,
|
73
|
-
exclude_domains: t.Sequence[str] | None,
|
74
|
-
include_answer: bool,
|
75
|
-
include_raw_content: bool,
|
76
|
-
include_images: bool,
|
77
|
-
use_cache: bool,
|
78
|
-
response: TavilyResponse,
|
79
|
-
) -> "TavilyResponseModel":
|
80
|
-
return TavilyResponseModel(
|
81
|
-
agent_id=agent_id,
|
82
|
-
query=query,
|
83
|
-
search_depth=search_depth,
|
84
|
-
topic=topic,
|
85
|
-
max_results=max_results,
|
86
|
-
include_domains=sorted(include_domains) if include_domains else None,
|
87
|
-
exclude_domains=sorted(exclude_domains) if exclude_domains else None,
|
88
|
-
include_answer=include_answer,
|
89
|
-
include_raw_content=include_raw_content,
|
90
|
-
include_images=include_images,
|
91
|
-
use_cache=use_cache,
|
92
|
-
datetime_=utcnow(),
|
93
|
-
response=response.model_dump(),
|
94
|
-
)
|
9
|
+
from prediction_market_agent_tooling.tools.tavily.tavily_models import (
|
10
|
+
TavilyResponse,
|
11
|
+
TavilyResponseModel,
|
12
|
+
)
|
13
|
+
from prediction_market_agent_tooling.tools.utils import utcnow
|
95
14
|
|
96
15
|
|
97
16
|
class TavilyStorage:
|
@@ -119,6 +38,7 @@ class TavilyStorage:
|
|
119
38
|
query: str,
|
120
39
|
search_depth: t.Literal["basic", "advanced"],
|
121
40
|
topic: t.Literal["general", "news"],
|
41
|
+
days: int | None,
|
122
42
|
max_results: int,
|
123
43
|
include_domains: t.Sequence[str] | None,
|
124
44
|
exclude_domains: t.Sequence[str] | None,
|
@@ -134,6 +54,7 @@ class TavilyStorage:
|
|
134
54
|
search_depth=search_depth,
|
135
55
|
topic=topic,
|
136
56
|
max_results=max_results,
|
57
|
+
days=days,
|
137
58
|
include_domains=include_domains,
|
138
59
|
exclude_domains=exclude_domains,
|
139
60
|
include_answer=include_answer,
|
@@ -152,6 +73,7 @@ class TavilyStorage:
|
|
152
73
|
query: str,
|
153
74
|
search_depth: t.Literal["basic", "advanced"],
|
154
75
|
topic: t.Literal["general", "news"],
|
76
|
+
days: int | None,
|
155
77
|
max_results: int,
|
156
78
|
include_domains: t.Sequence[str] | None,
|
157
79
|
exclude_domains: t.Sequence[str] | None,
|
@@ -167,6 +89,7 @@ class TavilyStorage:
|
|
167
89
|
.where(TavilyResponseModel.query == query)
|
168
90
|
.where(TavilyResponseModel.search_depth == search_depth)
|
169
91
|
.where(TavilyResponseModel.topic == topic)
|
92
|
+
.where(TavilyResponseModel.days == days)
|
170
93
|
.where(TavilyResponseModel.max_results == max_results)
|
171
94
|
.where(TavilyResponseModel.include_domains == include_domains)
|
172
95
|
.where(TavilyResponseModel.exclude_domains == exclude_domains)
|
@@ -18,6 +18,7 @@ from prediction_market_agent_tooling.gtypes import (
|
|
18
18
|
SecretStr,
|
19
19
|
)
|
20
20
|
from prediction_market_agent_tooling.loggers import logger
|
21
|
+
from prediction_market_agent_tooling.markets.market_fees import MarketFees
|
21
22
|
|
22
23
|
T = TypeVar("T")
|
23
24
|
|
@@ -189,7 +190,7 @@ def calculate_sell_amount_in_collateral(
|
|
189
190
|
shares_to_sell: float,
|
190
191
|
holdings: float,
|
191
192
|
other_holdings: float,
|
192
|
-
|
193
|
+
fees: MarketFees,
|
193
194
|
) -> float:
|
194
195
|
"""
|
195
196
|
Computes the amount of collateral that needs to be sold to get `shares`
|
@@ -198,16 +199,12 @@ def calculate_sell_amount_in_collateral(
|
|
198
199
|
Taken from https://github.com/protofire/omen-exchange/blob/29d0ab16bdafa5cc0d37933c1c7608a055400c73/app/src/util/tools/fpmm/trading/index.ts#L99
|
199
200
|
Simplified for binary markets.
|
200
201
|
"""
|
201
|
-
|
202
|
-
if not (0 <= fee < 1.0):
|
203
|
-
raise ValueError("Fee must be between 0 and 1")
|
204
|
-
|
205
202
|
for v in [shares_to_sell, holdings, other_holdings]:
|
206
203
|
if v <= 0:
|
207
204
|
raise ValueError("All share args must be greater than 0")
|
208
205
|
|
209
206
|
def f(r: float) -> float:
|
210
|
-
R = r / (1 -
|
207
|
+
R = (r + fees.absolute) / (1 - fees.bet_proportion)
|
211
208
|
first_term = other_holdings - R
|
212
209
|
second_term = holdings + shares_to_sell - R
|
213
210
|
third_term = holdings * other_holdings
|
@@ -17,9 +17,9 @@ prediction_market_agent_tooling/benchmark/agents.py,sha256=B1-uWdyeN4GGKMWGK_-Cc
|
|
17
17
|
prediction_market_agent_tooling/benchmark/benchmark.py,sha256=MqTiaaJ3cYiOLUVR7OyImLWxcEya3Rl5JyFYW-K0lwM,17097
|
18
18
|
prediction_market_agent_tooling/benchmark/utils.py,sha256=D0MfUkVZllmvcU0VOurk9tcKT7JTtwwOp-63zuCBVuc,2880
|
19
19
|
prediction_market_agent_tooling/config.py,sha256=WC30Nr16RGueTafA9i67OIB-6KDHZRryhiLPzebg9_I,6740
|
20
|
-
prediction_market_agent_tooling/deploy/agent.py,sha256=
|
20
|
+
prediction_market_agent_tooling/deploy/agent.py,sha256=3yookl4xiLLcIzvE5h-Rf6_NmBRZuKDsJu9vFJaSCtk,22294
|
21
21
|
prediction_market_agent_tooling/deploy/agent_example.py,sha256=dIIdZashExWk9tOdyDjw87AuUcGyM7jYxNChYrVK2dM,1001
|
22
|
-
prediction_market_agent_tooling/deploy/betting_strategy.py,sha256=
|
22
|
+
prediction_market_agent_tooling/deploy/betting_strategy.py,sha256=6PceKCNa4CM8ws033JFoCDb2Xm9OmtMjCtdSWXD5BpQ,12495
|
23
23
|
prediction_market_agent_tooling/deploy/constants.py,sha256=M5ty8URipYMGe_G-RzxRydK3AFL6CyvmqCraJUrLBnE,82
|
24
24
|
prediction_market_agent_tooling/deploy/gcp/deploy.py,sha256=CYUgnfy-9XVk04kkxA_5yp0GE9Mw5caYqlFUZQ2j3ks,3739
|
25
25
|
prediction_market_agent_tooling/deploy/gcp/kubernetes_models.py,sha256=OsPboCFGiZKsvGyntGZHwdqPlLTthITkNF5rJFvGgU8,2582
|
@@ -28,30 +28,31 @@ prediction_market_agent_tooling/gtypes.py,sha256=tqp03PyY0Yhievl4XELfwAn0xOoecaT
|
|
28
28
|
prediction_market_agent_tooling/jobs/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
29
29
|
prediction_market_agent_tooling/jobs/jobs.py,sha256=I07yh0GJ-xhlvQaOUQB8xlSnihhcbU2c7DZ4ZND14c0,1246
|
30
30
|
prediction_market_agent_tooling/jobs/jobs_models.py,sha256=I5uBTHJ2S1Wi3H4jDxxU7nsswSIP9r3BevHmljLh5Pg,1370
|
31
|
-
prediction_market_agent_tooling/jobs/omen/omen_jobs.py,sha256=
|
31
|
+
prediction_market_agent_tooling/jobs/omen/omen_jobs.py,sha256=I2_vGrEJj1reSI8M377ab5QCsYNp_l4l4QeYEmDBkFM,3989
|
32
32
|
prediction_market_agent_tooling/loggers.py,sha256=Am6HHXRNO545BO3l7Ue9Wb2TkYE1OK8KKhGbI3XypVU,3751
|
33
|
-
prediction_market_agent_tooling/markets/agent_market.py,sha256=
|
33
|
+
prediction_market_agent_tooling/markets/agent_market.py,sha256=09Guz5y5Uq0K8nKFmy8SqxNfCYJX3auyxWLzIuOek2I,11119
|
34
34
|
prediction_market_agent_tooling/markets/categorize.py,sha256=jsoHWvZk9pU6n17oWSCcCxNNYVwlb_NXsZxKRI7vmsk,1301
|
35
35
|
prediction_market_agent_tooling/markets/data_models.py,sha256=jMqrSFO_w2z-5N3PFVgZqTHdVdkzSDhhzky2lHsGGKA,3621
|
36
36
|
prediction_market_agent_tooling/markets/manifold/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
37
37
|
prediction_market_agent_tooling/markets/manifold/api.py,sha256=Fd0HYnstvvHO6AZkp1xiRlvCwQUc8kLR8DAj6PAZu0s,7297
|
38
38
|
prediction_market_agent_tooling/markets/manifold/data_models.py,sha256=ylXIEHymx2RcCdOVUpKP4BcTqbzLu_Fd5gV1TGBntVk,6099
|
39
|
-
prediction_market_agent_tooling/markets/manifold/manifold.py,sha256=
|
39
|
+
prediction_market_agent_tooling/markets/manifold/manifold.py,sha256=IeVS3Suifryym-aLFDNEyyhdT3Gg03l4O39-PDpXmE8,4895
|
40
40
|
prediction_market_agent_tooling/markets/manifold/utils.py,sha256=cPPFWXm3vCYH1jy7_ctJZuQH9ZDaPL4_AgAYzGWkoow,513
|
41
|
+
prediction_market_agent_tooling/markets/market_fees.py,sha256=Q64T9uaJx0Vllt0BkrPmpMEz53ra-hMVY8Czi7CEP7s,1227
|
41
42
|
prediction_market_agent_tooling/markets/markets.py,sha256=_3nV9QTT48G2oJ2egkuWA1UzrTOGY6x3mXqIRgDaVIo,3245
|
42
43
|
prediction_market_agent_tooling/markets/metaculus/api.py,sha256=4TRPGytQQbSdf42DCg2M_JWYPAuNjqZ3eBqaQBLkNks,2736
|
43
44
|
prediction_market_agent_tooling/markets/metaculus/data_models.py,sha256=2wDZ0BmK9O5Lud-q-FCzgW0tsK9GxMU0rUMlcPxSS04,3184
|
44
|
-
prediction_market_agent_tooling/markets/metaculus/metaculus.py,sha256=
|
45
|
+
prediction_market_agent_tooling/markets/metaculus/metaculus.py,sha256=d5tHhekLfPGO6CfmWWJQWpJu5HyoAWBM2aGSR874Cms,3695
|
45
46
|
prediction_market_agent_tooling/markets/omen/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
46
47
|
prediction_market_agent_tooling/markets/omen/data_models.py,sha256=nCjsc-ylIzQOCK_1BW-5NoYrS-NIXz2Hg9N1-IqhhC8,27516
|
47
|
-
prediction_market_agent_tooling/markets/omen/omen.py,sha256=
|
48
|
-
prediction_market_agent_tooling/markets/omen/omen_contracts.py,sha256=
|
48
|
+
prediction_market_agent_tooling/markets/omen/omen.py,sha256=3BF3oH78K_puwGktLkkOIxsa3G61Jb2zx6LZ_kjr3Hk,48638
|
49
|
+
prediction_market_agent_tooling/markets/omen/omen_contracts.py,sha256=Zq7SncCq-hvpgXKsVruGBGCn1OhKZTe7r1qLdCTrT2w,28297
|
49
50
|
prediction_market_agent_tooling/markets/omen/omen_resolving.py,sha256=iDWdjICGkt968exwCjY-6nsnQyrrNAg3YjnDdP430GQ,9415
|
50
|
-
prediction_market_agent_tooling/markets/omen/omen_subgraph_handler.py,sha256=
|
51
|
+
prediction_market_agent_tooling/markets/omen/omen_subgraph_handler.py,sha256=zQH3iu0SVH1RmE-W3NMEpcKVMILXJYxMhL6w1wh5RUo,37348
|
51
52
|
prediction_market_agent_tooling/markets/polymarket/api.py,sha256=UZ4_TG8ceb9Y-qgsOKs8Qiv8zDt957QkT8IX2c83yqo,4800
|
52
53
|
prediction_market_agent_tooling/markets/polymarket/data_models.py,sha256=Fd5PI5y3mJM8VHExBhWFWEnuuIKxQmIAXgBuoPDvNjw,4341
|
53
54
|
prediction_market_agent_tooling/markets/polymarket/data_models_web.py,sha256=VZhVccTApygSKMmy6Au2G02JCJOKJnR_oVeKlaesuSg,12548
|
54
|
-
prediction_market_agent_tooling/markets/polymarket/polymarket.py,sha256=
|
55
|
+
prediction_market_agent_tooling/markets/polymarket/polymarket.py,sha256=NRoZK71PtH8kkangMqme7twcAXhRJSSabbmOir-UnAI,3418
|
55
56
|
prediction_market_agent_tooling/markets/polymarket/utils.py,sha256=m4JG6WULh5epCJt4XBMHg0ae5NoVhqlOvAl0A7DR9iM,2023
|
56
57
|
prediction_market_agent_tooling/monitor/markets/manifold.py,sha256=TS4ERwTfQnot8dhekNyVNhJYf5ysYsjF-9v5_kM3aVI,3334
|
57
58
|
prediction_market_agent_tooling/monitor/markets/metaculus.py,sha256=LOnyWWBFdg10-cTWdb76nOsNjDloO8OfMT85GBzRCFI,1455
|
@@ -62,8 +63,8 @@ prediction_market_agent_tooling/monitor/monitor_app.py,sha256=zNHSwH_KEiv8aOwvfo
|
|
62
63
|
prediction_market_agent_tooling/monitor/monitor_settings.py,sha256=Xiozs3AsufuJ04JOe1vjUri-IAMWHjjmc2ugGGiHNH4,947
|
63
64
|
prediction_market_agent_tooling/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
64
65
|
prediction_market_agent_tooling/tools/balances.py,sha256=nR8_dSfbm3yTOOmMAwhGlurftEiNo1w1WIVzbskjdmM,837
|
65
|
-
prediction_market_agent_tooling/tools/betting_strategies/kelly_criterion.py,sha256=
|
66
|
-
prediction_market_agent_tooling/tools/betting_strategies/market_moving.py,sha256=
|
66
|
+
prediction_market_agent_tooling/tools/betting_strategies/kelly_criterion.py,sha256=TKVF8qZhxgz-4TTEHr7iVLfpurIhuji38qLc8CYaiXA,4662
|
67
|
+
prediction_market_agent_tooling/tools/betting_strategies/market_moving.py,sha256=J63kFdDlTHLjWwHE5pDp1fArWpuU6hNNFVpGLE1aRYw,5269
|
67
68
|
prediction_market_agent_tooling/tools/betting_strategies/minimum_bet_to_win.py,sha256=-FUSuQQgjcWSSnoFxnlAyTeilY6raJABJVM2QKkFqAY,438
|
68
69
|
prediction_market_agent_tooling/tools/betting_strategies/stretch_bet_between.py,sha256=THMXwFlskvzbjnX_OiYtDSzI8XVFyULWfP2525_9UGc,429
|
69
70
|
prediction_market_agent_tooling/tools/betting_strategies/utils.py,sha256=kpIb-ci67Vc1Yqqaa-_S4OUkbhWSIYog4_Iwp69HU_k,97
|
@@ -81,18 +82,19 @@ prediction_market_agent_tooling/tools/ipfs/ipfs_handler.py,sha256=CTTMfTvs_8PH4k
|
|
81
82
|
prediction_market_agent_tooling/tools/is_invalid.py,sha256=Lc5fWB4fmx7tFvRakmUOzo0Oq5EizorddZ2xjesEopY,4984
|
82
83
|
prediction_market_agent_tooling/tools/is_predictable.py,sha256=NIoR2bTNMmADcyNY2aKNMWkiDw7Z_9kZMcFXEdyewy4,6771
|
83
84
|
prediction_market_agent_tooling/tools/langfuse_.py,sha256=jI_4ROxqo41CCnWGS1vN_AeDVhRzLMaQLxH3kxDu3L8,1153
|
84
|
-
prediction_market_agent_tooling/tools/langfuse_client_utils.py,sha256=
|
85
|
+
prediction_market_agent_tooling/tools/langfuse_client_utils.py,sha256=B0PhAQyviFnVbtOCYMxYmcCn66cu9nbqAOIAZcdgiRI,5771
|
85
86
|
prediction_market_agent_tooling/tools/omen/reality_accuracy.py,sha256=M1SF7iSW1gVlQSTskdVFTn09uPLST23YeipVIWj54io,2236
|
86
87
|
prediction_market_agent_tooling/tools/parallelism.py,sha256=6Gou0hbjtMZrYvxjTDFUDZuxmE2nqZVbb6hkg1hF82A,1022
|
87
88
|
prediction_market_agent_tooling/tools/safe.py,sha256=h0xOO0eNtitClf0fPkn-0oTc6A_bflDTee98V_aiV-A,5195
|
88
89
|
prediction_market_agent_tooling/tools/singleton.py,sha256=CiIELUiI-OeS7U7eeHEt0rnVhtQGzwoUdAgn_7u_GBM,729
|
89
90
|
prediction_market_agent_tooling/tools/streamlit_user_login.py,sha256=NXEqfjT9Lc9QtliwSGRASIz1opjQ7Btme43H4qJbzgE,3010
|
90
|
-
prediction_market_agent_tooling/tools/
|
91
|
-
prediction_market_agent_tooling/tools/
|
92
|
-
prediction_market_agent_tooling/tools/
|
91
|
+
prediction_market_agent_tooling/tools/tavily/tavily_models.py,sha256=Rz4tZzwCRzPaq49SFT33SCRQrqHXtqWdD9ajb2tGCWc,2723
|
92
|
+
prediction_market_agent_tooling/tools/tavily/tavily_search.py,sha256=MK_ozeQbJ014HGiKFPDScjFYq0OGcjY1KPgc9A6qO0M,4511
|
93
|
+
prediction_market_agent_tooling/tools/tavily/tavily_storage.py,sha256=t-tZzbCzBBdFedRZDuVBn3A3mIDX8Z5wza6SxWswu_E,4093
|
94
|
+
prediction_market_agent_tooling/tools/utils.py,sha256=W-9SqeCKd51BYMRhDjYPQ7lfNO_zE9EvYpmu2r5WXGA,7163
|
93
95
|
prediction_market_agent_tooling/tools/web3_utils.py,sha256=dkcjG-LtuaWRh7WEMzRGmZ5B5rsxZTlliFOI6fj-EJ8,11842
|
94
|
-
prediction_market_agent_tooling-0.
|
95
|
-
prediction_market_agent_tooling-0.
|
96
|
-
prediction_market_agent_tooling-0.
|
97
|
-
prediction_market_agent_tooling-0.
|
98
|
-
prediction_market_agent_tooling-0.
|
96
|
+
prediction_market_agent_tooling-0.53.0.dist-info/LICENSE,sha256=6or154nLLU6bELzjh0mCreFjt0m2v72zLi3yHE0QbeE,7650
|
97
|
+
prediction_market_agent_tooling-0.53.0.dist-info/METADATA,sha256=RrGcyPVn2Gs6Xx9ezoDYzqUMAQfDZVlT63_Uq22GjSc,8056
|
98
|
+
prediction_market_agent_tooling-0.53.0.dist-info/WHEEL,sha256=Nq82e9rUAnEjt98J6MlVmMCZb-t9cYE2Ir1kpBmnWfs,88
|
99
|
+
prediction_market_agent_tooling-0.53.0.dist-info/entry_points.txt,sha256=m8PukHbeH5g0IAAmOf_1Ahm-sGAMdhSSRQmwtpmi2s8,81
|
100
|
+
prediction_market_agent_tooling-0.53.0.dist-info/RECORD,,
|
File without changes
|
File without changes
|