prediction-market-agent-tooling 0.61.2.dev479__py3-none-any.whl → 0.62.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/config.py +2 -3
- prediction_market_agent_tooling/deploy/agent.py +4 -5
- prediction_market_agent_tooling/deploy/betting_strategy.py +53 -69
- prediction_market_agent_tooling/gtypes.py +105 -27
- prediction_market_agent_tooling/jobs/jobs_models.py +5 -7
- prediction_market_agent_tooling/jobs/omen/omen_jobs.py +13 -17
- prediction_market_agent_tooling/markets/agent_market.py +96 -53
- prediction_market_agent_tooling/markets/blockchain_utils.py +1 -27
- prediction_market_agent_tooling/markets/data_models.py +40 -44
- prediction_market_agent_tooling/markets/manifold/api.py +2 -6
- prediction_market_agent_tooling/markets/manifold/data_models.py +33 -25
- prediction_market_agent_tooling/markets/manifold/manifold.py +13 -11
- prediction_market_agent_tooling/markets/market_fees.py +6 -2
- prediction_market_agent_tooling/markets/omen/data_models.py +66 -57
- prediction_market_agent_tooling/markets/omen/omen.py +222 -250
- prediction_market_agent_tooling/markets/omen/omen_contracts.py +31 -53
- prediction_market_agent_tooling/markets/omen/omen_resolving.py +7 -14
- prediction_market_agent_tooling/markets/omen/omen_subgraph_handler.py +20 -14
- prediction_market_agent_tooling/markets/polymarket/data_models.py +3 -3
- prediction_market_agent_tooling/markets/polymarket/data_models_web.py +4 -4
- prediction_market_agent_tooling/markets/polymarket/polymarket.py +3 -5
- prediction_market_agent_tooling/markets/seer/data_models.py +8 -8
- prediction_market_agent_tooling/markets/seer/seer.py +85 -71
- prediction_market_agent_tooling/markets/seer/seer_contracts.py +10 -5
- prediction_market_agent_tooling/markets/seer/seer_subgraph_handler.py +5 -2
- prediction_market_agent_tooling/monitor/monitor.py +2 -2
- prediction_market_agent_tooling/tools/_generic_value.py +261 -0
- prediction_market_agent_tooling/tools/balances.py +14 -11
- prediction_market_agent_tooling/tools/betting_strategies/kelly_criterion.py +12 -10
- prediction_market_agent_tooling/tools/betting_strategies/market_moving.py +31 -24
- prediction_market_agent_tooling/tools/betting_strategies/utils.py +3 -1
- prediction_market_agent_tooling/tools/contract.py +14 -10
- prediction_market_agent_tooling/tools/cow/cow_manager.py +3 -4
- prediction_market_agent_tooling/tools/cow/cow_order.py +51 -7
- prediction_market_agent_tooling/tools/langfuse_client_utils.py +13 -1
- prediction_market_agent_tooling/tools/omen/sell_positions.py +6 -3
- prediction_market_agent_tooling/tools/safe.py +5 -6
- prediction_market_agent_tooling/tools/tokens/auto_deposit.py +36 -27
- prediction_market_agent_tooling/tools/tokens/auto_withdraw.py +4 -25
- prediction_market_agent_tooling/tools/tokens/main_token.py +2 -2
- prediction_market_agent_tooling/tools/tokens/token_utils.py +46 -0
- prediction_market_agent_tooling/tools/tokens/usd.py +79 -0
- prediction_market_agent_tooling/tools/utils.py +14 -8
- prediction_market_agent_tooling/tools/web3_utils.py +24 -41
- {prediction_market_agent_tooling-0.61.2.dev479.dist-info → prediction_market_agent_tooling-0.62.0.dist-info}/METADATA +2 -1
- {prediction_market_agent_tooling-0.61.2.dev479.dist-info → prediction_market_agent_tooling-0.62.0.dist-info}/RECORD +49 -47
- prediction_market_agent_tooling/abis/gvp2_settlement.abi.json +0 -89
- {prediction_market_agent_tooling-0.61.2.dev479.dist-info → prediction_market_agent_tooling-0.62.0.dist-info}/LICENSE +0 -0
- {prediction_market_agent_tooling-0.61.2.dev479.dist-info → prediction_market_agent_tooling-0.62.0.dist-info}/WHEEL +0 -0
- {prediction_market_agent_tooling-0.61.2.dev479.dist-info → prediction_market_agent_tooling-0.62.0.dist-info}/entry_points.txt +0 -0
@@ -4,15 +4,16 @@ from eth_typing import ChecksumAddress
|
|
4
4
|
from web3 import Web3
|
5
5
|
from web3.types import TxReceipt
|
6
6
|
|
7
|
-
from prediction_market_agent_tooling.config import APIKeys
|
7
|
+
from prediction_market_agent_tooling.config import APIKeys, RPCConfig
|
8
8
|
from prediction_market_agent_tooling.gtypes import (
|
9
|
+
USD,
|
10
|
+
CollateralToken,
|
9
11
|
HexAddress,
|
10
12
|
HexBytes,
|
11
13
|
OutcomeStr,
|
12
|
-
|
13
|
-
|
14
|
+
OutcomeToken,
|
15
|
+
OutcomeWei,
|
14
16
|
xDai,
|
15
|
-
xdai_type,
|
16
17
|
)
|
17
18
|
from prediction_market_agent_tooling.loggers import logger
|
18
19
|
from prediction_market_agent_tooling.markets.agent_market import (
|
@@ -23,15 +24,9 @@ from prediction_market_agent_tooling.markets.agent_market import (
|
|
23
24
|
SortBy,
|
24
25
|
)
|
25
26
|
from prediction_market_agent_tooling.markets.blockchain_utils import store_trades
|
26
|
-
from prediction_market_agent_tooling.markets.data_models import
|
27
|
-
BetAmount,
|
28
|
-
Currency,
|
29
|
-
Position,
|
30
|
-
TokenAmount,
|
31
|
-
)
|
27
|
+
from prediction_market_agent_tooling.markets.data_models import ExistingPosition
|
32
28
|
from prediction_market_agent_tooling.markets.market_fees import MarketFees
|
33
29
|
from prediction_market_agent_tooling.markets.omen.omen import OmenAgentMarket
|
34
|
-
from prediction_market_agent_tooling.markets.omen.omen_contracts import sDaiContract
|
35
30
|
from prediction_market_agent_tooling.markets.seer.data_models import (
|
36
31
|
NewMarketEvent,
|
37
32
|
SeerMarket,
|
@@ -43,7 +38,6 @@ from prediction_market_agent_tooling.markets.seer.seer_contracts import (
|
|
43
38
|
from prediction_market_agent_tooling.markets.seer.seer_subgraph_handler import (
|
44
39
|
SeerSubgraphHandler,
|
45
40
|
)
|
46
|
-
from prediction_market_agent_tooling.tools.balances import get_balances
|
47
41
|
from prediction_market_agent_tooling.tools.contract import (
|
48
42
|
ContractERC20OnGnosisChain,
|
49
43
|
init_collateral_token_contract,
|
@@ -57,14 +51,16 @@ from prediction_market_agent_tooling.tools.datetime_utc import DatetimeUTC
|
|
57
51
|
from prediction_market_agent_tooling.tools.tokens.auto_deposit import (
|
58
52
|
auto_deposit_collateral_token,
|
59
53
|
)
|
60
|
-
from prediction_market_agent_tooling.tools.
|
54
|
+
from prediction_market_agent_tooling.tools.tokens.usd import (
|
55
|
+
get_token_in_usd,
|
56
|
+
get_usd_in_token,
|
57
|
+
)
|
61
58
|
|
62
59
|
# We place a larger bet amount by default than Omen so that cow presents valid quotes.
|
63
|
-
SEER_TINY_BET_AMOUNT =
|
60
|
+
SEER_TINY_BET_AMOUNT = USD(0.1)
|
64
61
|
|
65
62
|
|
66
63
|
class SeerAgentMarket(AgentMarket):
|
67
|
-
currency = Currency.sDai
|
68
64
|
wrapped_tokens: list[ChecksumAddress]
|
69
65
|
creator: HexAddress
|
70
66
|
collateral_token_contract_address_checksummed: ChecksumAddress
|
@@ -74,6 +70,16 @@ class SeerAgentMarket(AgentMarket):
|
|
74
70
|
None # Seer markets don't have a description, so just default to None.
|
75
71
|
)
|
76
72
|
|
73
|
+
def get_collateral_token_contract(
|
74
|
+
self, web3: Web3 | None = None
|
75
|
+
) -> ContractERC20OnGnosisChain:
|
76
|
+
web3 = web3 or RPCConfig().get_web3()
|
77
|
+
return to_gnosis_chain_contract(
|
78
|
+
init_collateral_token_contract(
|
79
|
+
self.collateral_token_contract_address_checksummed, web3
|
80
|
+
)
|
81
|
+
)
|
82
|
+
|
77
83
|
def store_prediction(
|
78
84
|
self,
|
79
85
|
processed_market: ProcessedMarket | None,
|
@@ -95,29 +101,28 @@ class SeerAgentMarket(AgentMarket):
|
|
95
101
|
agent_name=agent_name,
|
96
102
|
)
|
97
103
|
|
98
|
-
def
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
)
|
104
|
+
def get_token_in_usd(self, x: CollateralToken) -> USD:
|
105
|
+
return get_token_in_usd(x, self.collateral_token_contract_address_checksummed)
|
106
|
+
|
107
|
+
def get_usd_in_token(self, x: USD) -> CollateralToken:
|
108
|
+
return get_usd_in_token(x, self.collateral_token_contract_address_checksummed)
|
104
109
|
|
105
110
|
def get_buy_token_amount(
|
106
|
-
self, bet_amount:
|
107
|
-
) ->
|
111
|
+
self, bet_amount: USD | CollateralToken, direction: bool
|
112
|
+
) -> OutcomeToken:
|
108
113
|
"""Returns number of outcome tokens returned for a given bet expressed in collateral units."""
|
109
114
|
|
110
115
|
outcome_token = self.get_wrapped_token_for_outcome(direction)
|
111
|
-
|
112
|
-
bet_amount_in_wei =
|
116
|
+
bet_amount_in_tokens = self.get_in_token(bet_amount)
|
117
|
+
bet_amount_in_wei = bet_amount_in_tokens.as_wei
|
113
118
|
|
114
119
|
quote = CowManager().get_quote(
|
115
120
|
buy_token=outcome_token,
|
116
121
|
sell_amount=bet_amount_in_wei,
|
117
122
|
collateral_token=self.collateral_token_contract_address_checksummed,
|
118
123
|
)
|
119
|
-
sell_amount =
|
120
|
-
return
|
124
|
+
sell_amount = OutcomeWei(quote.quote.buyAmount.root).as_outcome_token
|
125
|
+
return sell_amount
|
121
126
|
|
122
127
|
def get_outcome_str_from_bool(self, outcome: bool) -> OutcomeStr:
|
123
128
|
outcome_translated = SeerOutcomeEnum.from_bool(outcome)
|
@@ -125,33 +130,46 @@ class SeerAgentMarket(AgentMarket):
|
|
125
130
|
return OutcomeStr(self.outcomes[idx])
|
126
131
|
|
127
132
|
@staticmethod
|
128
|
-
def get_trade_balance(api_keys: APIKeys) ->
|
133
|
+
def get_trade_balance(api_keys: APIKeys) -> USD:
|
129
134
|
return OmenAgentMarket.get_trade_balance(api_keys=api_keys)
|
130
135
|
|
131
|
-
|
132
|
-
|
133
|
-
return BetAmount(amount=SEER_TINY_BET_AMOUNT, currency=cls.currency)
|
136
|
+
def get_tiny_bet_amount(self) -> CollateralToken:
|
137
|
+
return self.get_in_token(SEER_TINY_BET_AMOUNT)
|
134
138
|
|
135
|
-
def get_position(
|
139
|
+
def get_position(
|
140
|
+
self, user_id: str, web3: Web3 | None = None
|
141
|
+
) -> ExistingPosition | None:
|
136
142
|
"""
|
137
143
|
Fetches position from the user in a given market.
|
138
144
|
We ignore the INVALID balances since we are only interested in binary outcomes.
|
139
145
|
"""
|
140
146
|
|
141
|
-
|
147
|
+
amounts_ot: dict[OutcomeStr, OutcomeToken] = {}
|
142
148
|
|
143
149
|
for outcome in [True, False]:
|
144
150
|
wrapped_token = self.get_wrapped_token_for_outcome(outcome)
|
145
151
|
|
146
|
-
|
147
|
-
address=wrapped_token
|
148
|
-
|
149
|
-
|
150
|
-
amounts[outcome_str] = TokenAmount(
|
151
|
-
amount=wei_to_xdai(outcome_token_balance), currency=self.currency
|
152
|
+
outcome_token_balance_wei = OutcomeWei.from_wei(
|
153
|
+
ContractERC20OnGnosisChain(address=wrapped_token).balanceOf(
|
154
|
+
for_address=Web3.to_checksum_address(user_id), web3=web3
|
155
|
+
)
|
152
156
|
)
|
153
|
-
|
154
|
-
|
157
|
+
outcome_str = self.get_outcome_str_from_bool(outcome=outcome)
|
158
|
+
amounts_ot[outcome_str] = outcome_token_balance_wei.as_outcome_token
|
159
|
+
|
160
|
+
amounts_current = {
|
161
|
+
k: self.get_token_in_usd(self.get_sell_value_of_outcome_token(k, v))
|
162
|
+
for k, v in amounts_ot.items()
|
163
|
+
}
|
164
|
+
amounts_potential = {
|
165
|
+
k: self.get_token_in_usd(v.as_token) for k, v in amounts_ot.items()
|
166
|
+
}
|
167
|
+
return ExistingPosition(
|
168
|
+
market_id=self.id,
|
169
|
+
amounts_current=amounts_current,
|
170
|
+
amounts_potential=amounts_potential,
|
171
|
+
amounts_ot=amounts_ot,
|
172
|
+
)
|
155
173
|
|
156
174
|
@staticmethod
|
157
175
|
def get_user_id(api_keys: APIKeys) -> str:
|
@@ -210,9 +228,9 @@ class SeerAgentMarket(AgentMarket):
|
|
210
228
|
CowManager().get_quote(
|
211
229
|
collateral_token=self.collateral_token_contract_address_checksummed,
|
212
230
|
buy_token=outcome_token,
|
213
|
-
sell_amount=
|
214
|
-
|
215
|
-
), # we take 1
|
231
|
+
sell_amount=CollateralToken(
|
232
|
+
1
|
233
|
+
).as_wei, # we take 1 as a baseline value for common trades the agents take.
|
216
234
|
)
|
217
235
|
return True
|
218
236
|
except NoLiquidityAvailableOnCowException:
|
@@ -236,7 +254,7 @@ class SeerAgentMarket(AgentMarket):
|
|
236
254
|
def place_bet(
|
237
255
|
self,
|
238
256
|
outcome: bool,
|
239
|
-
amount:
|
257
|
+
amount: USD,
|
240
258
|
auto_deposit: bool = True,
|
241
259
|
web3: Web3 | None = None,
|
242
260
|
api_keys: APIKeys | None = None,
|
@@ -247,32 +265,27 @@ class SeerAgentMarket(AgentMarket):
|
|
247
265
|
f"Market {self.id} is not open for trading. Cannot place bet."
|
248
266
|
)
|
249
267
|
|
250
|
-
|
251
|
-
|
268
|
+
amount_in_token = self.get_usd_in_token(amount)
|
269
|
+
amount_wei = amount_in_token.as_wei
|
270
|
+
collateral_contract = self.get_collateral_token_contract()
|
252
271
|
|
253
|
-
collateral_contract = sDaiContract()
|
254
272
|
if auto_deposit:
|
255
|
-
# We convert the deposit amount (in sDai) to assets in order to convert.
|
256
|
-
asset_amount = collateral_contract.convertToAssets(
|
257
|
-
xdai_to_wei(xdai_type(amount.amount))
|
258
|
-
)
|
259
273
|
auto_deposit_collateral_token(
|
260
|
-
collateral_contract,
|
274
|
+
collateral_contract, amount_wei, api_keys, web3
|
261
275
|
)
|
262
276
|
|
263
|
-
|
264
|
-
collateral_balance
|
265
|
-
if collateral_balance.sdai < amount.amount:
|
277
|
+
collateral_balance = collateral_contract.balanceOf(api_keys.bet_from_address)
|
278
|
+
if collateral_balance < amount_wei:
|
266
279
|
raise ValueError(
|
267
|
-
f"Balance {collateral_balance
|
280
|
+
f"Balance {collateral_balance} not enough for bet size {amount}"
|
268
281
|
)
|
269
282
|
|
270
283
|
outcome_token = self.get_wrapped_token_for_outcome(outcome)
|
271
|
-
#
|
284
|
+
# Sell using token address
|
272
285
|
order_metadata = CowManager().swap(
|
273
|
-
amount=
|
286
|
+
amount=amount_in_token,
|
274
287
|
sell_token=collateral_contract.address,
|
275
|
-
buy_token=
|
288
|
+
buy_token=outcome_token,
|
276
289
|
api_keys=api_keys,
|
277
290
|
web3=web3,
|
278
291
|
)
|
@@ -282,18 +295,17 @@ class SeerAgentMarket(AgentMarket):
|
|
282
295
|
|
283
296
|
def seer_create_market_tx(
|
284
297
|
api_keys: APIKeys,
|
285
|
-
initial_funds:
|
298
|
+
initial_funds: USD | CollateralToken,
|
286
299
|
question: str,
|
287
300
|
opening_time: DatetimeUTC,
|
288
301
|
language: str,
|
289
|
-
outcomes:
|
302
|
+
outcomes: t.Sequence[OutcomeStr],
|
290
303
|
auto_deposit: bool,
|
291
304
|
category: str,
|
292
305
|
min_bond_xdai: xDai,
|
293
306
|
web3: Web3 | None = None,
|
294
307
|
) -> ChecksumAddress:
|
295
308
|
web3 = web3 or SeerMarketFactory.get_web3() # Default to Gnosis web3.
|
296
|
-
initial_funds_wei = xdai_to_wei(initial_funds)
|
297
309
|
|
298
310
|
factory_contract = SeerMarketFactory()
|
299
311
|
collateral_token_address = factory_contract.collateral_token(web3=web3)
|
@@ -301,24 +313,26 @@ def seer_create_market_tx(
|
|
301
313
|
init_collateral_token_contract(collateral_token_address, web3)
|
302
314
|
)
|
303
315
|
|
316
|
+
initial_funds_in_collateral = (
|
317
|
+
get_usd_in_token(initial_funds, collateral_token_address)
|
318
|
+
if isinstance(initial_funds, USD)
|
319
|
+
else initial_funds
|
320
|
+
)
|
321
|
+
initial_funds_in_collateral_wei = initial_funds_in_collateral.as_wei
|
322
|
+
|
304
323
|
if auto_deposit:
|
305
324
|
auto_deposit_collateral_token(
|
306
325
|
collateral_token_contract=collateral_token_contract,
|
307
326
|
api_keys=api_keys,
|
308
|
-
|
327
|
+
collateral_amount_wei_or_usd=initial_funds_in_collateral_wei,
|
309
328
|
web3=web3,
|
310
329
|
)
|
311
330
|
|
312
|
-
# In case of ERC4626, obtained (for example) sDai out of xDai could be lower than the `amount_wei`, so we need to handle it.
|
313
|
-
initial_funds_in_shares = collateral_token_contract.get_in_shares(
|
314
|
-
amount=initial_funds_wei, web3=web3
|
315
|
-
)
|
316
|
-
|
317
331
|
# Approve the market maker to withdraw our collateral token.
|
318
332
|
collateral_token_contract.approve(
|
319
333
|
api_keys=api_keys,
|
320
334
|
for_address=factory_contract.address,
|
321
|
-
amount_wei=
|
335
|
+
amount_wei=initial_funds_in_collateral_wei,
|
322
336
|
web3=web3,
|
323
337
|
)
|
324
338
|
|
@@ -329,7 +343,7 @@ def seer_create_market_tx(
|
|
329
343
|
opening_time=opening_time,
|
330
344
|
language=language,
|
331
345
|
category=category,
|
332
|
-
|
346
|
+
min_bond=min_bond_xdai,
|
333
347
|
)
|
334
348
|
tx_receipt = factory_contract.create_categorical_market(
|
335
349
|
api_keys=api_keys, params=params, web3=web3
|
@@ -1,10 +1,16 @@
|
|
1
1
|
import os
|
2
|
+
import typing as t
|
2
3
|
|
3
4
|
from web3 import Web3
|
4
5
|
from web3.types import TxReceipt
|
5
6
|
|
6
7
|
from prediction_market_agent_tooling.config import APIKeys
|
7
|
-
from prediction_market_agent_tooling.gtypes import
|
8
|
+
from prediction_market_agent_tooling.gtypes import (
|
9
|
+
ABI,
|
10
|
+
ChecksumAddress,
|
11
|
+
OutcomeStr,
|
12
|
+
xDai,
|
13
|
+
)
|
8
14
|
from prediction_market_agent_tooling.markets.seer.data_models import (
|
9
15
|
CreateCategoricalMarketsParams,
|
10
16
|
)
|
@@ -13,7 +19,6 @@ from prediction_market_agent_tooling.tools.contract import (
|
|
13
19
|
abi_field_validator,
|
14
20
|
)
|
15
21
|
from prediction_market_agent_tooling.tools.datetime_utc import DatetimeUTC
|
16
|
-
from prediction_market_agent_tooling.tools.web3_utils import xdai_to_wei
|
17
22
|
|
18
23
|
|
19
24
|
class SeerMarketFactory(ContractOnGnosisChain):
|
@@ -31,9 +36,9 @@ class SeerMarketFactory(ContractOnGnosisChain):
|
|
31
36
|
@staticmethod
|
32
37
|
def build_market_params(
|
33
38
|
market_question: str,
|
34
|
-
outcomes:
|
39
|
+
outcomes: t.Sequence[OutcomeStr],
|
35
40
|
opening_time: DatetimeUTC,
|
36
|
-
|
41
|
+
min_bond: xDai,
|
37
42
|
language: str = "en_US",
|
38
43
|
category: str = "misc",
|
39
44
|
) -> CreateCategoricalMarketsParams:
|
@@ -42,7 +47,7 @@ class SeerMarketFactory(ContractOnGnosisChain):
|
|
42
47
|
token_names=[
|
43
48
|
o.upper() for o in outcomes
|
44
49
|
], # Following usual token names on Seer (YES,NO).
|
45
|
-
min_bond=
|
50
|
+
min_bond=min_bond.as_xdai_wei.value,
|
46
51
|
opening_time=int(opening_time.timestamp()),
|
47
52
|
outcomes=outcomes,
|
48
53
|
lang=language,
|
@@ -15,6 +15,7 @@ from prediction_market_agent_tooling.markets.seer.data_models import (
|
|
15
15
|
)
|
16
16
|
from prediction_market_agent_tooling.tools.hexbytes_custom import HexBytes
|
17
17
|
from prediction_market_agent_tooling.tools.utils import to_int_timestamp, utcnow
|
18
|
+
from prediction_market_agent_tooling.tools.web3_utils import unwrap_generic_value
|
18
19
|
|
19
20
|
|
20
21
|
class SeerSubgraphHandler(BaseSubgraphHandler):
|
@@ -158,7 +159,7 @@ class SeerSubgraphHandler(BaseSubgraphHandler):
|
|
158
159
|
first=(
|
159
160
|
limit if limit else sys.maxsize
|
160
161
|
), # if not limit, we fetch all possible markets,
|
161
|
-
where=where_stms,
|
162
|
+
where=unwrap_generic_value(where_stms),
|
162
163
|
**optional_params,
|
163
164
|
)
|
164
165
|
fields = self._get_fields_for_markets(markets_field)
|
@@ -219,7 +220,9 @@ class SeerSubgraphHandler(BaseSubgraphHandler):
|
|
219
220
|
{"token1": wrapped_token.lower()},
|
220
221
|
]
|
221
222
|
)
|
222
|
-
pools_field = self.swapr_algebra_subgraph.Query.pools(
|
223
|
+
pools_field = self.swapr_algebra_subgraph.Query.pools(
|
224
|
+
where=unwrap_generic_value({"or": wheres})
|
225
|
+
)
|
223
226
|
fields = self._get_fields_for_pools(pools_field)
|
224
227
|
pools = self.do_query(fields=fields, pydantic_model=SeerPool)
|
225
228
|
return pools
|
@@ -186,12 +186,12 @@ def monitor_agent(agent: DeployedAgent) -> None:
|
|
186
186
|
return
|
187
187
|
bets_info = {
|
188
188
|
"Market Question": [bet.market_question for bet in agent_bets],
|
189
|
-
"Bet Amount": [bet.amount
|
189
|
+
"Bet Amount": [bet.amount for bet in agent_bets],
|
190
190
|
"Bet Outcome": [bet.outcome for bet in agent_bets],
|
191
191
|
"Created Time": [bet.created_time for bet in agent_bets],
|
192
192
|
"Resolved Time": [bet.resolved_time for bet in agent_bets],
|
193
193
|
"Is Correct": [bet.is_correct for bet in agent_bets],
|
194
|
-
"Profit": [round(bet.profit
|
194
|
+
"Profit": [round(bet.profit, 2) for bet in agent_bets],
|
195
195
|
}
|
196
196
|
|
197
197
|
# Time column to use for x-axes and sorting
|
@@ -0,0 +1,261 @@
|
|
1
|
+
import typing as t
|
2
|
+
from decimal import Decimal
|
3
|
+
from typing import TypeVar, overload
|
4
|
+
|
5
|
+
from pydantic import GetCoreSchemaHandler
|
6
|
+
from pydantic_core import CoreSchema, core_schema
|
7
|
+
from web3.types import Wei as WeiWeb3
|
8
|
+
|
9
|
+
InputValueType = TypeVar(
|
10
|
+
"InputValueType", bound=t.Union[str, int, float, WeiWeb3, Decimal]
|
11
|
+
)
|
12
|
+
InternalValueType = TypeVar("InternalValueType", bound=t.Union[int, float, WeiWeb3])
|
13
|
+
|
14
|
+
|
15
|
+
class _GenericValue(
|
16
|
+
t.Generic[InputValueType, InternalValueType],
|
17
|
+
# Not great, but it allows to serialize this object with plain json.
|
18
|
+
dict[t.Literal["value"] | t.Literal["type"], InternalValueType | str],
|
19
|
+
):
|
20
|
+
"""
|
21
|
+
A helper class intended for inheritance. Do not instantiate this class directly.
|
22
|
+
|
23
|
+
Example:
|
24
|
+
|
25
|
+
```python
|
26
|
+
a = _GenericValue(10)
|
27
|
+
b = Token(100) # Token is a subclass of _GenericValue
|
28
|
+
c = xDai(100) # xDai is a subclass of _GenericValue
|
29
|
+
d = Mana(100) # Mana is a subclass of _GenericValue
|
30
|
+
e = xDai(50)
|
31
|
+
|
32
|
+
# Mypy will complain if we try to work with different currencies (types)
|
33
|
+
b - c # mypy will report incompatible types
|
34
|
+
c - d # mypy will report incompatible types
|
35
|
+
c - e # mypy will be ok
|
36
|
+
a - b # mypy won't report issues, as others are subclasses of _GenericValue, and that's a problem, so don't use _GenericValue directly
|
37
|
+
|
38
|
+
# Resulting types after arithmetic operations are as expected, so we don't need to wrap them as before (e.g. xdai_type(c + c))
|
39
|
+
x = c - e # x is of type xDai
|
40
|
+
x = c * e # x if of type xDai
|
41
|
+
x = c / e # x is of type float (pure value after division with same types)
|
42
|
+
x = c / 2 # x is of type xDai
|
43
|
+
x = c // 2 # x is of type xDai
|
44
|
+
x * x * 2 # x is of type xDai
|
45
|
+
```
|
46
|
+
|
47
|
+
TODO: There are some type ignores which isn't cool, but it works and type-wise values are also correct. Idk how to explain it to mypy though.
|
48
|
+
"""
|
49
|
+
|
50
|
+
GenericValueType = TypeVar(
|
51
|
+
"GenericValueType", bound="_GenericValue[InputValueType, InternalValueType]"
|
52
|
+
)
|
53
|
+
|
54
|
+
parser: t.Callable[[InputValueType], InternalValueType]
|
55
|
+
|
56
|
+
def __init_subclass__(
|
57
|
+
cls, parser: t.Callable[[InputValueType], InternalValueType]
|
58
|
+
) -> None:
|
59
|
+
super().__init_subclass__()
|
60
|
+
cls.parser = parser
|
61
|
+
|
62
|
+
def __init__(self, value: InputValueType) -> None:
|
63
|
+
self.value: InternalValueType = self.parser(value)
|
64
|
+
super().__init__({"value": self.value, "type": self.__class__.__name__})
|
65
|
+
|
66
|
+
def __str__(self) -> str:
|
67
|
+
return f"{self.value}"
|
68
|
+
|
69
|
+
def __neg__(self: GenericValueType) -> GenericValueType:
|
70
|
+
return type(self)(-self.value) # type: ignore[arg-type]
|
71
|
+
|
72
|
+
def __abs__(self: GenericValueType) -> GenericValueType:
|
73
|
+
return type(self)(abs(self.value)) # type: ignore[arg-type]
|
74
|
+
|
75
|
+
def __sub__(
|
76
|
+
self: GenericValueType, other: GenericValueType | t.Literal[0]
|
77
|
+
) -> GenericValueType:
|
78
|
+
if other == 0:
|
79
|
+
other = self.zero()
|
80
|
+
if not isinstance(other, _GenericValue):
|
81
|
+
raise TypeError("Cannot subtract different types")
|
82
|
+
if type(self) is not type(other):
|
83
|
+
raise TypeError("Cannot subtract different types")
|
84
|
+
return type(self)(self.value - other.value)
|
85
|
+
|
86
|
+
def __add__(
|
87
|
+
self: GenericValueType, other: GenericValueType | t.Literal[0]
|
88
|
+
) -> GenericValueType:
|
89
|
+
if other == 0:
|
90
|
+
other = self.zero()
|
91
|
+
if not isinstance(other, _GenericValue):
|
92
|
+
raise TypeError("Cannot add different types")
|
93
|
+
if type(self) is not type(other):
|
94
|
+
raise TypeError("Cannot add different types")
|
95
|
+
return type(self)(self.value + other.value)
|
96
|
+
|
97
|
+
def __mul__(
|
98
|
+
self: GenericValueType, other: GenericValueType | int | float
|
99
|
+
) -> GenericValueType:
|
100
|
+
if not isinstance(other, (_GenericValue, int, float)):
|
101
|
+
raise TypeError("Cannot multiply different types")
|
102
|
+
if not isinstance(other, (int, float)) and type(self) is not type(other):
|
103
|
+
raise TypeError("Cannot multiply different types")
|
104
|
+
return type(self)(self.value * (other if isinstance(other, (int, float)) else other.value)) # type: ignore
|
105
|
+
|
106
|
+
@overload
|
107
|
+
def __truediv__(self: GenericValueType, other: int | float) -> GenericValueType:
|
108
|
+
...
|
109
|
+
|
110
|
+
@overload
|
111
|
+
def __truediv__(
|
112
|
+
self: GenericValueType, other: GenericValueType
|
113
|
+
) -> InternalValueType:
|
114
|
+
...
|
115
|
+
|
116
|
+
def __truediv__(
|
117
|
+
self: GenericValueType, other: GenericValueType | int | float
|
118
|
+
) -> GenericValueType | InternalValueType:
|
119
|
+
if not isinstance(other, (_GenericValue, int, float)):
|
120
|
+
raise TypeError("Cannot multiply different types")
|
121
|
+
if not isinstance(other, (int, float)) and type(self) is not type(other):
|
122
|
+
raise TypeError("Cannot multiply different types")
|
123
|
+
if other == 0:
|
124
|
+
raise ZeroDivisionError("Cannot divide by zero")
|
125
|
+
if isinstance(other, (int, float)):
|
126
|
+
return type(self)(self.value / other) # type: ignore
|
127
|
+
else:
|
128
|
+
return self.value / other.value # type: ignore
|
129
|
+
|
130
|
+
@overload
|
131
|
+
def __floordiv__(self: GenericValueType, other: int | float) -> GenericValueType:
|
132
|
+
...
|
133
|
+
|
134
|
+
@overload
|
135
|
+
def __floordiv__(
|
136
|
+
self: GenericValueType, other: GenericValueType
|
137
|
+
) -> InternalValueType:
|
138
|
+
...
|
139
|
+
|
140
|
+
def __floordiv__(
|
141
|
+
self: GenericValueType, other: GenericValueType | int | float
|
142
|
+
) -> GenericValueType | InternalValueType:
|
143
|
+
if not isinstance(other, (_GenericValue, int, float)):
|
144
|
+
raise TypeError("Cannot multiply different types")
|
145
|
+
if not isinstance(other, (int, float)) and type(self) is not type(other):
|
146
|
+
raise TypeError("Cannot multiply different types")
|
147
|
+
if other == 0:
|
148
|
+
raise ZeroDivisionError("Cannot divide by zero")
|
149
|
+
if isinstance(other, (int, float)):
|
150
|
+
return type(self)(self.value // other) # type: ignore
|
151
|
+
else:
|
152
|
+
return self.value // other.value # type: ignore
|
153
|
+
|
154
|
+
def __lt__(self: GenericValueType, other: GenericValueType | t.Literal[0]) -> bool:
|
155
|
+
if other == 0:
|
156
|
+
other = self.zero()
|
157
|
+
if not isinstance(other, _GenericValue):
|
158
|
+
raise TypeError("Cannot compare different types")
|
159
|
+
if type(self) is not type(other):
|
160
|
+
raise TypeError("Cannot compare different types")
|
161
|
+
return bool(self.value < other.value)
|
162
|
+
|
163
|
+
def __le__(self: GenericValueType, other: GenericValueType | t.Literal[0]) -> bool:
|
164
|
+
if other == 0:
|
165
|
+
other = self.zero()
|
166
|
+
if not isinstance(other, _GenericValue):
|
167
|
+
raise TypeError("Cannot compare different types")
|
168
|
+
if type(self) is not type(other):
|
169
|
+
raise TypeError("Cannot compare different types")
|
170
|
+
return bool(self.value <= other.value)
|
171
|
+
|
172
|
+
def __gt__(self: GenericValueType, other: GenericValueType | t.Literal[0]) -> bool:
|
173
|
+
if other == 0:
|
174
|
+
other = self.zero()
|
175
|
+
if not isinstance(other, _GenericValue):
|
176
|
+
raise TypeError("Cannot compare different types")
|
177
|
+
if type(self) is not type(other):
|
178
|
+
raise TypeError("Cannot compare different types")
|
179
|
+
return bool(self.value > other.value)
|
180
|
+
|
181
|
+
def __ge__(self: GenericValueType, other: GenericValueType | t.Literal[0]) -> bool:
|
182
|
+
if other == 0:
|
183
|
+
other = self.zero()
|
184
|
+
if not isinstance(other, _GenericValue):
|
185
|
+
raise TypeError("Cannot compare different types")
|
186
|
+
if type(self) is not type(other):
|
187
|
+
raise TypeError("Cannot compare different types")
|
188
|
+
return bool(self.value >= other.value)
|
189
|
+
|
190
|
+
def __eq__(self: GenericValueType, other: GenericValueType | t.Literal[0]) -> bool: # type: ignore
|
191
|
+
if other == 0:
|
192
|
+
other = self.zero()
|
193
|
+
if not isinstance(other, _GenericValue):
|
194
|
+
raise TypeError("Cannot compare different types")
|
195
|
+
if type(self) is not type(other):
|
196
|
+
raise TypeError("Cannot compare different types")
|
197
|
+
return bool(self.value == other.value)
|
198
|
+
|
199
|
+
def __ne__(self: GenericValueType, other: GenericValueType | t.Literal[0]) -> bool: # type: ignore
|
200
|
+
if other == 0:
|
201
|
+
other = self.zero()
|
202
|
+
if not isinstance(other, _GenericValue):
|
203
|
+
raise TypeError("Cannot compare different types")
|
204
|
+
if type(self) is not type(other):
|
205
|
+
raise TypeError("Cannot compare different types")
|
206
|
+
return bool(self.value != other.value)
|
207
|
+
|
208
|
+
def __repr__(self) -> str:
|
209
|
+
return f"{type(self).__name__}({self.value})"
|
210
|
+
|
211
|
+
def __float__(self) -> float:
|
212
|
+
return float(self.value)
|
213
|
+
|
214
|
+
def __radd__(
|
215
|
+
self: GenericValueType, other: GenericValueType | t.Literal[0] | int | float
|
216
|
+
) -> GenericValueType:
|
217
|
+
if isinstance(other, (_GenericValue, int, float)):
|
218
|
+
return self.__add__(other) # type: ignore[operator]
|
219
|
+
|
220
|
+
elif isinstance(other, (int, float)) and other == 0:
|
221
|
+
return self
|
222
|
+
|
223
|
+
else:
|
224
|
+
raise TypeError("Cannot add different types")
|
225
|
+
|
226
|
+
def __round__(self: GenericValueType, ndigits: int = 0) -> GenericValueType:
|
227
|
+
if not isinstance(self.value, (int, float)):
|
228
|
+
raise TypeError("Cannot round non-numeric types")
|
229
|
+
return type(self)(round(self.value, ndigits)) # type: ignore[arg-type]
|
230
|
+
|
231
|
+
def __bool__(self) -> bool:
|
232
|
+
return bool(self.value)
|
233
|
+
|
234
|
+
@classmethod
|
235
|
+
def __get_pydantic_core_schema__(
|
236
|
+
cls, source_type: t.Any, handler: GetCoreSchemaHandler
|
237
|
+
) -> CoreSchema:
|
238
|
+
# Support for Pydantic usage.
|
239
|
+
dt_schema = handler(str | int | float | dict)
|
240
|
+
return core_schema.no_info_after_validator_function(
|
241
|
+
lambda x: cls(x["value"] if isinstance(x, dict) else x),
|
242
|
+
dt_schema,
|
243
|
+
)
|
244
|
+
|
245
|
+
def with_fraction(self: GenericValueType, fraction: float) -> GenericValueType:
|
246
|
+
if not 0 <= fraction <= 1:
|
247
|
+
raise ValueError(f"Given fraction {fraction} is not in the range [0,1].")
|
248
|
+
return self.__class__(self.value * (1 + fraction)) # type: ignore[arg-type]
|
249
|
+
|
250
|
+
def without_fraction(self: GenericValueType, fraction: float) -> GenericValueType:
|
251
|
+
if not 0 <= fraction <= 1:
|
252
|
+
raise ValueError(f"Given fraction {fraction} is not in the range [0,1].")
|
253
|
+
return self.__class__(self.value * (1 - fraction)) # type: ignore[arg-type]
|
254
|
+
|
255
|
+
@classmethod
|
256
|
+
def zero(cls: type[GenericValueType]) -> GenericValueType:
|
257
|
+
return cls(0) # type: ignore[arg-type]
|
258
|
+
|
259
|
+
@property
|
260
|
+
def symbol(self) -> str:
|
261
|
+
return self.__class__.__name__
|