prediction-market-agent-tooling 0.48.18__py3-none-any.whl → 0.49.1__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/abis/debuggingcontract.abi.json +29 -0
- prediction_market_agent_tooling/abis/omen_agentresultmapping.abi.json +171 -0
- prediction_market_agent_tooling/benchmark/benchmark.py +0 -93
- prediction_market_agent_tooling/config.py +16 -0
- prediction_market_agent_tooling/deploy/agent.py +86 -13
- prediction_market_agent_tooling/deploy/betting_strategy.py +5 -35
- prediction_market_agent_tooling/jobs/omen/omen_jobs.py +2 -1
- prediction_market_agent_tooling/markets/agent_market.py +14 -6
- prediction_market_agent_tooling/markets/data_models.py +14 -0
- prediction_market_agent_tooling/markets/manifold/api.py +3 -1
- prediction_market_agent_tooling/markets/manifold/manifold.py +7 -2
- prediction_market_agent_tooling/markets/metaculus/metaculus.py +6 -1
- prediction_market_agent_tooling/markets/omen/data_models.py +247 -6
- prediction_market_agent_tooling/markets/omen/omen.py +77 -43
- prediction_market_agent_tooling/markets/omen/omen_contracts.py +179 -33
- prediction_market_agent_tooling/markets/omen/omen_subgraph_handler.py +35 -0
- prediction_market_agent_tooling/markets/polymarket/polymarket.py +1 -1
- prediction_market_agent_tooling/monitor/markets/polymarket.py +4 -0
- prediction_market_agent_tooling/monitor/monitor.py +3 -3
- prediction_market_agent_tooling/monitor/monitor_app.py +2 -2
- prediction_market_agent_tooling/tools/contract.py +50 -1
- prediction_market_agent_tooling/tools/ipfs/ipfs_handler.py +33 -0
- prediction_market_agent_tooling/tools/langfuse_client_utils.py +27 -12
- prediction_market_agent_tooling/tools/utils.py +28 -4
- prediction_market_agent_tooling/tools/web3_utils.py +7 -0
- {prediction_market_agent_tooling-0.48.18.dist-info → prediction_market_agent_tooling-0.49.1.dist-info}/METADATA +2 -1
- {prediction_market_agent_tooling-0.48.18.dist-info → prediction_market_agent_tooling-0.49.1.dist-info}/RECORD +30 -27
- {prediction_market_agent_tooling-0.48.18.dist-info → prediction_market_agent_tooling-0.49.1.dist-info}/LICENSE +0 -0
- {prediction_market_agent_tooling-0.48.18.dist-info → prediction_market_agent_tooling-0.49.1.dist-info}/WHEEL +0 -0
- {prediction_market_agent_tooling-0.48.18.dist-info → prediction_market_agent_tooling-0.49.1.dist-info}/entry_points.txt +0 -0
@@ -0,0 +1,29 @@
|
|
1
|
+
[
|
2
|
+
{
|
3
|
+
"constant": false,
|
4
|
+
"inputs": [],
|
5
|
+
"name": "inc",
|
6
|
+
"outputs": [],
|
7
|
+
"payable": false,
|
8
|
+
"stateMutability": "nonpayable",
|
9
|
+
"type": "function"
|
10
|
+
},
|
11
|
+
{
|
12
|
+
"constant": true,
|
13
|
+
"inputs": [],
|
14
|
+
"name": "counter",
|
15
|
+
"outputs": [{ "name": "", "type": "uint256" }],
|
16
|
+
"payable": false,
|
17
|
+
"stateMutability": "view",
|
18
|
+
"type": "function"
|
19
|
+
},
|
20
|
+
{
|
21
|
+
"constant": true,
|
22
|
+
"inputs": [],
|
23
|
+
"name": "getNow",
|
24
|
+
"outputs": [{ "name": "", "type": "uint32" }],
|
25
|
+
"payable": false,
|
26
|
+
"stateMutability": "view",
|
27
|
+
"type": "function"
|
28
|
+
}
|
29
|
+
]
|
@@ -0,0 +1,171 @@
|
|
1
|
+
[
|
2
|
+
{
|
3
|
+
"type": "constructor",
|
4
|
+
"inputs": [],
|
5
|
+
"stateMutability": "nonpayable"
|
6
|
+
},
|
7
|
+
{
|
8
|
+
"type": "function",
|
9
|
+
"name": "addPrediction",
|
10
|
+
"inputs": [
|
11
|
+
{
|
12
|
+
"name": "marketAddress",
|
13
|
+
"type": "address",
|
14
|
+
"internalType": "address"
|
15
|
+
},
|
16
|
+
{
|
17
|
+
"name": "prediction",
|
18
|
+
"type": "tuple",
|
19
|
+
"internalType": "struct Prediction",
|
20
|
+
"components": [
|
21
|
+
{
|
22
|
+
"name": "publisherAddress",
|
23
|
+
"type": "address",
|
24
|
+
"internalType": "address"
|
25
|
+
},
|
26
|
+
{
|
27
|
+
"name": "ipfsHash",
|
28
|
+
"type": "bytes32",
|
29
|
+
"internalType": "bytes32"
|
30
|
+
},
|
31
|
+
{
|
32
|
+
"name": "txHashes",
|
33
|
+
"type": "bytes32[]",
|
34
|
+
"internalType": "bytes32[]"
|
35
|
+
},
|
36
|
+
{
|
37
|
+
"name": "estimatedProbabilityBps",
|
38
|
+
"type": "uint16",
|
39
|
+
"internalType": "uint16"
|
40
|
+
}
|
41
|
+
]
|
42
|
+
}
|
43
|
+
],
|
44
|
+
"outputs": [],
|
45
|
+
"stateMutability": "nonpayable"
|
46
|
+
},
|
47
|
+
{
|
48
|
+
"type": "function",
|
49
|
+
"name": "getPredictionByIndex",
|
50
|
+
"inputs": [
|
51
|
+
{
|
52
|
+
"name": "marketAddress",
|
53
|
+
"type": "address",
|
54
|
+
"internalType": "address"
|
55
|
+
},
|
56
|
+
{
|
57
|
+
"name": "index",
|
58
|
+
"type": "uint256",
|
59
|
+
"internalType": "uint256"
|
60
|
+
}
|
61
|
+
],
|
62
|
+
"outputs": [
|
63
|
+
{
|
64
|
+
"name": "",
|
65
|
+
"type": "tuple",
|
66
|
+
"internalType": "struct Prediction",
|
67
|
+
"components": [
|
68
|
+
{
|
69
|
+
"name": "publisherAddress",
|
70
|
+
"type": "address",
|
71
|
+
"internalType": "address"
|
72
|
+
},
|
73
|
+
{
|
74
|
+
"name": "ipfsHash",
|
75
|
+
"type": "bytes32",
|
76
|
+
"internalType": "bytes32"
|
77
|
+
},
|
78
|
+
{
|
79
|
+
"name": "txHashes",
|
80
|
+
"type": "bytes32[]",
|
81
|
+
"internalType": "bytes32[]"
|
82
|
+
},
|
83
|
+
{
|
84
|
+
"name": "estimatedProbabilityBps",
|
85
|
+
"type": "uint16",
|
86
|
+
"internalType": "uint16"
|
87
|
+
}
|
88
|
+
]
|
89
|
+
}
|
90
|
+
],
|
91
|
+
"stateMutability": "view"
|
92
|
+
},
|
93
|
+
{
|
94
|
+
"type": "function",
|
95
|
+
"name": "getPredictions",
|
96
|
+
"inputs": [
|
97
|
+
{
|
98
|
+
"name": "marketAddress",
|
99
|
+
"type": "address",
|
100
|
+
"internalType": "address"
|
101
|
+
}
|
102
|
+
],
|
103
|
+
"outputs": [
|
104
|
+
{
|
105
|
+
"name": "",
|
106
|
+
"type": "tuple[]",
|
107
|
+
"internalType": "struct Prediction[]",
|
108
|
+
"components": [
|
109
|
+
{
|
110
|
+
"name": "publisherAddress",
|
111
|
+
"type": "address",
|
112
|
+
"internalType": "address"
|
113
|
+
},
|
114
|
+
{
|
115
|
+
"name": "ipfsHash",
|
116
|
+
"type": "bytes32",
|
117
|
+
"internalType": "bytes32"
|
118
|
+
},
|
119
|
+
{
|
120
|
+
"name": "txHashes",
|
121
|
+
"type": "bytes32[]",
|
122
|
+
"internalType": "bytes32[]"
|
123
|
+
},
|
124
|
+
{
|
125
|
+
"name": "estimatedProbabilityBps",
|
126
|
+
"type": "uint16",
|
127
|
+
"internalType": "uint16"
|
128
|
+
}
|
129
|
+
]
|
130
|
+
}
|
131
|
+
],
|
132
|
+
"stateMutability": "view"
|
133
|
+
},
|
134
|
+
{
|
135
|
+
"type": "event",
|
136
|
+
"name": "PredictionAdded",
|
137
|
+
"inputs": [
|
138
|
+
{
|
139
|
+
"name": "marketAddress",
|
140
|
+
"type": "address",
|
141
|
+
"indexed": true,
|
142
|
+
"internalType": "address"
|
143
|
+
},
|
144
|
+
{
|
145
|
+
"name": "estimatedProbabilityBps",
|
146
|
+
"type": "uint16",
|
147
|
+
"indexed": false,
|
148
|
+
"internalType": "uint16"
|
149
|
+
},
|
150
|
+
{
|
151
|
+
"name": "publisherAddress",
|
152
|
+
"type": "address",
|
153
|
+
"indexed": true,
|
154
|
+
"internalType": "address"
|
155
|
+
},
|
156
|
+
{
|
157
|
+
"name": "txHashes",
|
158
|
+
"type": "bytes32[]",
|
159
|
+
"indexed": false,
|
160
|
+
"internalType": "bytes32[]"
|
161
|
+
},
|
162
|
+
{
|
163
|
+
"name": "ipfsHash",
|
164
|
+
"type": "bytes32",
|
165
|
+
"indexed": false,
|
166
|
+
"internalType": "bytes32"
|
167
|
+
}
|
168
|
+
],
|
169
|
+
"anonymous": false
|
170
|
+
}
|
171
|
+
]
|
@@ -1,7 +1,6 @@
|
|
1
1
|
import concurrent.futures
|
2
2
|
import os
|
3
3
|
import typing as t
|
4
|
-
from collections import defaultdict
|
5
4
|
|
6
5
|
import numpy as np
|
7
6
|
import pandas as pd
|
@@ -436,92 +435,6 @@ class Benchmarker:
|
|
436
435
|
],
|
437
436
|
}
|
438
437
|
|
439
|
-
def calculate_expected_returns(
|
440
|
-
self, prediction: Prediction, market: AgentMarket
|
441
|
-
) -> float | None:
|
442
|
-
"""
|
443
|
-
The expected value if betting on a binary market in its initialized state of 50:50 'yes' and 'no' shares, with the assumption that the correct `p_yes` is that of the market.
|
444
|
-
"""
|
445
|
-
if not prediction.is_answered:
|
446
|
-
return None
|
447
|
-
|
448
|
-
# TODO: Add support for different bet sizes -- if we bet a low amount (such as <10 units), the real shares will be very close to that we calculate below (bet_units / share_price),
|
449
|
-
# but if one bets a lot, it will change the share price along the way, and so he/she receives less than `bet_units / share_price`, but it's more complicated to calculate.
|
450
|
-
bet_units = 10 # Assuming the agent always bet 10 units per market.
|
451
|
-
|
452
|
-
assert prediction.outcome_prediction is not None
|
453
|
-
# Assume that market starts at 50/50 and so the price is 0.5 at the time we are buying it,
|
454
|
-
# we can't use {yes,no}_outcome_price atm, because it would just cancel out to EV = 0.0,
|
455
|
-
# as it's the same as the probability.
|
456
|
-
yes_shares = (
|
457
|
-
bet_units / 0.5 # market.yes_outcome_price
|
458
|
-
if prediction.outcome_prediction.probable_resolution == Resolution.YES
|
459
|
-
and market.yes_outcome_price > 0
|
460
|
-
else 0
|
461
|
-
)
|
462
|
-
no_shares = (
|
463
|
-
bet_units / 0.5 # market.no_outcome_price
|
464
|
-
if prediction.outcome_prediction.probable_resolution == Resolution.NO
|
465
|
-
and market.no_outcome_price > 0
|
466
|
-
else 0
|
467
|
-
)
|
468
|
-
|
469
|
-
# If we don't bet, we don't have any expected returns.
|
470
|
-
if yes_shares == 0 and no_shares == 0:
|
471
|
-
return None
|
472
|
-
|
473
|
-
expected_value = (
|
474
|
-
yes_shares * market.current_p_yes
|
475
|
-
+ no_shares * (1 - market.current_p_yes)
|
476
|
-
- bet_units
|
477
|
-
)
|
478
|
-
expected_returns_perc = 100 * expected_value / bet_units
|
479
|
-
|
480
|
-
return expected_returns_perc
|
481
|
-
|
482
|
-
def compute_expected_returns_summary(
|
483
|
-
self,
|
484
|
-
) -> t.Tuple[dict[str, list[str | float]], dict[str, list[str | float | None]]]:
|
485
|
-
overall_summary: dict[str, list[str | float]] = defaultdict(list)
|
486
|
-
|
487
|
-
for agent in self.registered_agents:
|
488
|
-
expected_returns = []
|
489
|
-
|
490
|
-
for market in self.markets:
|
491
|
-
if (
|
492
|
-
prediction := self.get_prediction(agent.agent_name, market.question)
|
493
|
-
).is_answered and (
|
494
|
-
expected_return := self.calculate_expected_returns(
|
495
|
-
prediction, market
|
496
|
-
)
|
497
|
-
) is not None:
|
498
|
-
expected_returns.append(expected_return)
|
499
|
-
|
500
|
-
overall_summary["Agent"].append(agent.agent_name)
|
501
|
-
overall_summary["Mean expected returns"].append(
|
502
|
-
float(np.mean(expected_returns))
|
503
|
-
)
|
504
|
-
overall_summary["Median expected returns"].append(
|
505
|
-
float(np.median(expected_returns))
|
506
|
-
)
|
507
|
-
overall_summary["Total expected returns"].append(
|
508
|
-
float(np.sum(expected_returns))
|
509
|
-
)
|
510
|
-
|
511
|
-
per_market: dict[str, list[str | float | None]] = defaultdict(list)
|
512
|
-
|
513
|
-
for market in self.markets:
|
514
|
-
per_market["Market Question"].append(market.question)
|
515
|
-
|
516
|
-
for agent in self.registered_agents:
|
517
|
-
per_market[agent.agent_name].append(
|
518
|
-
self.calculate_expected_returns(
|
519
|
-
self.get_prediction(agent.agent_name, market.question), market
|
520
|
-
)
|
521
|
-
)
|
522
|
-
|
523
|
-
return dict(overall_summary), dict(per_market)
|
524
|
-
|
525
438
|
def generate_markdown_report(self) -> str:
|
526
439
|
md = "# Comparison Report\n\n"
|
527
440
|
md += "## Market Results\n\n"
|
@@ -533,10 +446,4 @@ class Benchmarker:
|
|
533
446
|
md += "\n\n"
|
534
447
|
md += "### Markets\n\n"
|
535
448
|
md += pd.DataFrame(self.get_markets_summary()).to_markdown(index=False)
|
536
|
-
md += "\n\n"
|
537
|
-
md += "### Expected value\n\n"
|
538
|
-
overall_summary, per_market = self.compute_expected_returns_summary()
|
539
|
-
md += pd.DataFrame(overall_summary).to_markdown(index=False)
|
540
|
-
md += "\n\n"
|
541
|
-
md += pd.DataFrame(per_market).to_markdown(index=False)
|
542
449
|
return md
|
@@ -35,6 +35,7 @@ class APIKeys(BaseSettings):
|
|
35
35
|
SAFE_ADDRESS: t.Optional[ChecksumAddress] = None
|
36
36
|
OPENAI_API_KEY: t.Optional[SecretStr] = None
|
37
37
|
GRAPH_API_KEY: t.Optional[SecretStr] = None
|
38
|
+
TENDERLY_FORK_RPC: t.Optional[str] = None
|
38
39
|
|
39
40
|
GOOGLE_SEARCH_API_KEY: t.Optional[SecretStr] = None
|
40
41
|
GOOGLE_SEARCH_ENGINE_ID: t.Optional[SecretStr] = None
|
@@ -44,6 +45,9 @@ class APIKeys(BaseSettings):
|
|
44
45
|
LANGFUSE_HOST: t.Optional[str] = None
|
45
46
|
LANGFUSE_DEPLOYMENT_VERSION: t.Optional[str] = None
|
46
47
|
|
48
|
+
PINATA_API_KEY: t.Optional[SecretStr] = None
|
49
|
+
PINATA_API_SECRET: t.Optional[SecretStr] = None
|
50
|
+
|
47
51
|
TAVILY_API_KEY: t.Optional[SecretStr] = None
|
48
52
|
|
49
53
|
SQLALCHEMY_DB_URL: t.Optional[SecretStr] = None
|
@@ -147,6 +151,18 @@ class APIKeys(BaseSettings):
|
|
147
151
|
and self.LANGFUSE_HOST is not None
|
148
152
|
)
|
149
153
|
|
154
|
+
@property
|
155
|
+
def pinata_api_key(self) -> SecretStr:
|
156
|
+
return check_not_none(
|
157
|
+
self.PINATA_API_KEY, "PINATA_API_KEY missing in the environment."
|
158
|
+
)
|
159
|
+
|
160
|
+
@property
|
161
|
+
def pinata_api_secret(self) -> SecretStr:
|
162
|
+
return check_not_none(
|
163
|
+
self.PINATA_API_SECRET, "PINATA_API_SECRET missing in the environment."
|
164
|
+
)
|
165
|
+
|
150
166
|
@property
|
151
167
|
def tavily_api_key(self) -> SecretStr:
|
152
168
|
return check_not_none(
|
@@ -10,6 +10,7 @@ from functools import cached_property
|
|
10
10
|
|
11
11
|
from pydantic import BaseModel, BeforeValidator, computed_field
|
12
12
|
from typing_extensions import Annotated
|
13
|
+
from web3 import Web3
|
13
14
|
|
14
15
|
from prediction_market_agent_tooling.config import APIKeys
|
15
16
|
from prediction_market_agent_tooling.deploy.betting_strategy import (
|
@@ -30,7 +31,7 @@ from prediction_market_agent_tooling.deploy.gcp.utils import (
|
|
30
31
|
gcp_function_is_active,
|
31
32
|
gcp_resolve_api_keys_secrets,
|
32
33
|
)
|
33
|
-
from prediction_market_agent_tooling.gtypes import xDai, xdai_type
|
34
|
+
from prediction_market_agent_tooling.gtypes import HexStr, xDai, xdai_type
|
34
35
|
from prediction_market_agent_tooling.loggers import logger
|
35
36
|
from prediction_market_agent_tooling.markets.agent_market import (
|
36
37
|
AgentMarket,
|
@@ -38,6 +39,7 @@ from prediction_market_agent_tooling.markets.agent_market import (
|
|
38
39
|
SortBy,
|
39
40
|
)
|
40
41
|
from prediction_market_agent_tooling.markets.data_models import (
|
42
|
+
PlacedTrade,
|
41
43
|
Position,
|
42
44
|
ProbabilisticAnswer,
|
43
45
|
Trade,
|
@@ -46,17 +48,27 @@ from prediction_market_agent_tooling.markets.markets import (
|
|
46
48
|
MarketType,
|
47
49
|
have_bet_on_market_since,
|
48
50
|
)
|
51
|
+
from prediction_market_agent_tooling.markets.omen.data_models import (
|
52
|
+
ContractPrediction,
|
53
|
+
IPFSAgentResult,
|
54
|
+
)
|
49
55
|
from prediction_market_agent_tooling.markets.omen.omen import (
|
50
56
|
is_minimum_required_balance,
|
51
57
|
redeem_from_all_user_positions,
|
52
58
|
withdraw_wxdai_to_xdai_to_keep_balance,
|
53
59
|
)
|
60
|
+
from prediction_market_agent_tooling.markets.omen.omen_contracts import (
|
61
|
+
OmenAgentResultMappingContract,
|
62
|
+
)
|
54
63
|
from prediction_market_agent_tooling.monitor.monitor_app import (
|
55
64
|
MARKET_TYPE_TO_DEPLOYED_AGENT,
|
56
65
|
)
|
66
|
+
from prediction_market_agent_tooling.tools.hexbytes_custom import HexBytes
|
67
|
+
from prediction_market_agent_tooling.tools.ipfs.ipfs_handler import IPFSHandler
|
57
68
|
from prediction_market_agent_tooling.tools.is_predictable import is_predictable_binary
|
58
69
|
from prediction_market_agent_tooling.tools.langfuse_ import langfuse_context, observe
|
59
70
|
from prediction_market_agent_tooling.tools.utils import DatetimeWithTimezone, utcnow
|
71
|
+
from prediction_market_agent_tooling.tools.web3_utils import ipfscidv0_to_byte32
|
60
72
|
|
61
73
|
MAX_AVAILABLE_MARKETS = 20
|
62
74
|
TRADER_TAG = "trader"
|
@@ -110,7 +122,7 @@ class OutOfFundsError(ValueError):
|
|
110
122
|
|
111
123
|
class ProcessedMarket(BaseModel):
|
112
124
|
answer: ProbabilisticAnswer
|
113
|
-
trades: list[
|
125
|
+
trades: list[PlacedTrade]
|
114
126
|
|
115
127
|
|
116
128
|
class AnsweredEnum(str, Enum):
|
@@ -282,7 +294,6 @@ class DeployableTraderAgent(DeployableAgent):
|
|
282
294
|
bet_on_n_markets_per_run: int = 1
|
283
295
|
min_required_balance_to_operate: xDai | None = xdai_type(1)
|
284
296
|
min_balance_to_keep_in_native_currency: xDai | None = xdai_type(0.1)
|
285
|
-
strategy: BettingStrategy = MaxAccuracyBettingStrategy()
|
286
297
|
|
287
298
|
def __init__(
|
288
299
|
self,
|
@@ -291,6 +302,16 @@ class DeployableTraderAgent(DeployableAgent):
|
|
291
302
|
) -> None:
|
292
303
|
super().__init__(enable_langfuse=enable_langfuse)
|
293
304
|
self.place_bet = place_bet
|
305
|
+
self.ipfs_handler = IPFSHandler(APIKeys())
|
306
|
+
|
307
|
+
def get_betting_strategy(self, market: AgentMarket) -> BettingStrategy:
|
308
|
+
user_id = market.get_user_id(api_keys=APIKeys())
|
309
|
+
|
310
|
+
total_amount = market.get_user_balance(user_id=user_id) * 0.1
|
311
|
+
if existing_position := market.get_position(user_id=user_id):
|
312
|
+
total_amount += existing_position.total_amount.amount
|
313
|
+
|
314
|
+
return MaxAccuracyBettingStrategy(bet_amount=total_amount)
|
294
315
|
|
295
316
|
def initialize_langfuse(self) -> None:
|
296
317
|
super().initialize_langfuse()
|
@@ -410,7 +431,8 @@ class DeployableTraderAgent(DeployableAgent):
|
|
410
431
|
answer: ProbabilisticAnswer,
|
411
432
|
existing_position: Position | None,
|
412
433
|
) -> list[Trade]:
|
413
|
-
|
434
|
+
strategy = self.get_betting_strategy(market=market)
|
435
|
+
trades = strategy.calculate_trades(existing_position, answer, market)
|
414
436
|
BettingStrategy.assert_trades_currency_match_markets(market, trades)
|
415
437
|
return trades
|
416
438
|
|
@@ -443,33 +465,84 @@ class DeployableTraderAgent(DeployableAgent):
|
|
443
465
|
|
444
466
|
existing_position = market.get_position(user_id=APIKeys().bet_from_address)
|
445
467
|
trades = self.build_trades(
|
446
|
-
market=market,
|
468
|
+
market=market,
|
469
|
+
answer=answer,
|
470
|
+
existing_position=existing_position,
|
447
471
|
)
|
448
472
|
|
473
|
+
placed_trades = []
|
449
474
|
if self.place_bet:
|
450
475
|
for trade in trades:
|
451
|
-
logger.info(f"Executing trade {trade}")
|
476
|
+
logger.info(f"Executing trade {trade} on market {market.id}")
|
452
477
|
|
453
478
|
match trade.trade_type:
|
454
479
|
case TradeType.BUY:
|
455
|
-
market.buy_tokens(
|
480
|
+
id = market.buy_tokens(
|
481
|
+
outcome=trade.outcome, amount=trade.amount
|
482
|
+
)
|
456
483
|
case TradeType.SELL:
|
457
|
-
market.sell_tokens(
|
484
|
+
id = market.sell_tokens(
|
485
|
+
outcome=trade.outcome, amount=trade.amount
|
486
|
+
)
|
458
487
|
case _:
|
459
488
|
raise ValueError(f"Unexpected trade type {trade.trade_type}.")
|
489
|
+
placed_trades.append(PlacedTrade.from_trade(trade, id))
|
460
490
|
|
461
|
-
|
462
|
-
|
463
|
-
processed_market = ProcessedMarket(answer=answer, trades=trades)
|
491
|
+
processed_market = ProcessedMarket(answer=answer, trades=placed_trades)
|
464
492
|
self.update_langfuse_trace_by_processed_market(market_type, processed_market)
|
465
493
|
|
494
|
+
self.after_process_market(
|
495
|
+
market_type, market, processed_market=processed_market
|
496
|
+
)
|
497
|
+
|
466
498
|
logger.info(f"Processed market {market.question=} from {market.url=}.")
|
467
499
|
return processed_market
|
468
500
|
|
469
501
|
def after_process_market(
|
470
|
-
self,
|
502
|
+
self,
|
503
|
+
market_type: MarketType,
|
504
|
+
market: AgentMarket,
|
505
|
+
processed_market: ProcessedMarket,
|
471
506
|
) -> None:
|
472
|
-
|
507
|
+
if market_type != MarketType.OMEN:
|
508
|
+
logger.info(
|
509
|
+
f"Skipping after_process_market since market_type {market_type} != OMEN"
|
510
|
+
)
|
511
|
+
return
|
512
|
+
keys = APIKeys()
|
513
|
+
self.store_prediction(
|
514
|
+
market_id=market.id, processed_market=processed_market, keys=keys
|
515
|
+
)
|
516
|
+
|
517
|
+
def store_prediction(
|
518
|
+
self, market_id: str, processed_market: ProcessedMarket, keys: APIKeys
|
519
|
+
) -> None:
|
520
|
+
reasoning = (
|
521
|
+
processed_market.answer.reasoning
|
522
|
+
if processed_market.answer.reasoning
|
523
|
+
else ""
|
524
|
+
)
|
525
|
+
ipfs_hash = self.ipfs_handler.store_agent_result(
|
526
|
+
IPFSAgentResult(reasoning=reasoning)
|
527
|
+
)
|
528
|
+
|
529
|
+
tx_hashes = [
|
530
|
+
HexBytes(HexStr(i.id)) for i in processed_market.trades if i.id is not None
|
531
|
+
]
|
532
|
+
prediction = ContractPrediction(
|
533
|
+
publisher=keys.public_key,
|
534
|
+
ipfs_hash=ipfscidv0_to_byte32(ipfs_hash),
|
535
|
+
tx_hashes=tx_hashes,
|
536
|
+
estimated_probability_bps=int(processed_market.answer.p_yes * 10000),
|
537
|
+
)
|
538
|
+
tx_receipt = OmenAgentResultMappingContract().add_prediction(
|
539
|
+
api_keys=keys,
|
540
|
+
market_address=Web3.to_checksum_address(market_id),
|
541
|
+
prediction=prediction,
|
542
|
+
)
|
543
|
+
logger.info(
|
544
|
+
f"Added prediction to market {market_id}. - receipt {tx_receipt['transactionHash'].hex()}."
|
545
|
+
)
|
473
546
|
|
474
547
|
def before_process_markets(self, market_type: MarketType) -> None:
|
475
548
|
"""
|
@@ -30,12 +30,6 @@ class BettingStrategy(ABC):
|
|
30
30
|
def build_zero_token_amount(self, currency: Currency) -> TokenAmount:
|
31
31
|
return TokenAmount(amount=0, currency=currency)
|
32
32
|
|
33
|
-
@abstractmethod
|
34
|
-
def adjust_bet_amount(
|
35
|
-
self, existing_position: Position | None, market: AgentMarket
|
36
|
-
) -> float:
|
37
|
-
pass
|
38
|
-
|
39
33
|
@staticmethod
|
40
34
|
def assert_trades_currency_match_markets(
|
41
35
|
market: AgentMarket, trades: list[Trade]
|
@@ -99,20 +93,7 @@ class BettingStrategy(ABC):
|
|
99
93
|
|
100
94
|
|
101
95
|
class MaxAccuracyBettingStrategy(BettingStrategy):
|
102
|
-
def
|
103
|
-
self, existing_position: Position | None, market: AgentMarket
|
104
|
-
) -> float:
|
105
|
-
existing_position_total_amount = (
|
106
|
-
existing_position.total_amount.amount if existing_position else 0
|
107
|
-
)
|
108
|
-
bet_amount = (
|
109
|
-
market.get_tiny_bet_amount().amount
|
110
|
-
if self.bet_amount is None
|
111
|
-
else self.bet_amount
|
112
|
-
)
|
113
|
-
return bet_amount + existing_position_total_amount
|
114
|
-
|
115
|
-
def __init__(self, bet_amount: float | None = None):
|
96
|
+
def __init__(self, bet_amount: float):
|
116
97
|
self.bet_amount = bet_amount
|
117
98
|
|
118
99
|
def calculate_trades(
|
@@ -121,13 +102,11 @@ class MaxAccuracyBettingStrategy(BettingStrategy):
|
|
121
102
|
answer: ProbabilisticAnswer,
|
122
103
|
market: AgentMarket,
|
123
104
|
) -> list[Trade]:
|
124
|
-
adjusted_bet_amount = self.adjust_bet_amount(existing_position, market)
|
125
|
-
|
126
105
|
direction = self.calculate_direction(market.current_p_yes, answer.p_yes)
|
127
106
|
|
128
107
|
amounts = {
|
129
108
|
market.get_outcome_str_from_bool(direction): TokenAmount(
|
130
|
-
amount=
|
109
|
+
amount=self.bet_amount,
|
131
110
|
currency=market.currency,
|
132
111
|
),
|
133
112
|
}
|
@@ -155,24 +134,15 @@ class MaxExpectedValueBettingStrategy(MaxAccuracyBettingStrategy):
|
|
155
134
|
|
156
135
|
|
157
136
|
class KellyBettingStrategy(BettingStrategy):
|
158
|
-
def __init__(self, max_bet_amount: float
|
137
|
+
def __init__(self, max_bet_amount: float):
|
159
138
|
self.max_bet_amount = max_bet_amount
|
160
139
|
|
161
|
-
def adjust_bet_amount(
|
162
|
-
self, existing_position: Position | None, market: AgentMarket
|
163
|
-
) -> float:
|
164
|
-
existing_position_total_amount = (
|
165
|
-
existing_position.total_amount.amount if existing_position else 0
|
166
|
-
)
|
167
|
-
return self.max_bet_amount + existing_position_total_amount
|
168
|
-
|
169
140
|
def calculate_trades(
|
170
141
|
self,
|
171
142
|
existing_position: Position | None,
|
172
143
|
answer: ProbabilisticAnswer,
|
173
144
|
market: AgentMarket,
|
174
145
|
) -> list[Trade]:
|
175
|
-
adjusted_bet_amount = self.adjust_bet_amount(existing_position, market)
|
176
146
|
outcome_token_pool = check_not_none(market.outcome_token_pool)
|
177
147
|
kelly_bet = (
|
178
148
|
get_kelly_bet_full(
|
@@ -183,12 +153,12 @@ class KellyBettingStrategy(BettingStrategy):
|
|
183
153
|
market.get_outcome_str_from_bool(False)
|
184
154
|
],
|
185
155
|
estimated_p_yes=answer.p_yes,
|
186
|
-
max_bet=
|
156
|
+
max_bet=self.max_bet_amount,
|
187
157
|
confidence=answer.confidence,
|
188
158
|
)
|
189
159
|
if market.has_token_pool()
|
190
160
|
else get_kelly_bet_simplified(
|
191
|
-
|
161
|
+
self.max_bet_amount,
|
192
162
|
market.current_p_yes,
|
193
163
|
answer.p_yes,
|
194
164
|
answer.confidence,
|
@@ -83,7 +83,8 @@ def compute_job_reward(
|
|
83
83
|
market: OmenAgentMarket, max_bond: float, web3: Web3 | None = None
|
84
84
|
) -> float:
|
85
85
|
# Because jobs are powered by prediction markets, potentional reward depends on job's liquidity and our will to bond (bet) our xDai into our job completion.
|
86
|
-
|
86
|
+
strategy = KellyBettingStrategy(max_bet_amount=max_bond)
|
87
|
+
required_trades = strategy.calculate_trades(
|
87
88
|
existing_position=None,
|
88
89
|
# We assume that we finish the job and so the probability of the market happening will be 100%.
|
89
90
|
answer=ProbabilisticAnswer(p_yes=Probability(1.0), confidence=1.0),
|