prediction-market-agent-tooling 0.13.1__py3-none-any.whl → 0.14.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.
Files changed (28) hide show
  1. prediction_market_agent_tooling/benchmark/utils.py +1 -1
  2. prediction_market_agent_tooling/config.py +7 -0
  3. prediction_market_agent_tooling/deploy/agent.py +0 -1
  4. prediction_market_agent_tooling/deploy/gcp/kubernetes_models.py +152 -0
  5. prediction_market_agent_tooling/deploy/gcp/utils.py +31 -0
  6. prediction_market_agent_tooling/gtypes.py +14 -16
  7. prediction_market_agent_tooling/markets/agent_market.py +14 -8
  8. prediction_market_agent_tooling/markets/data_models.py +15 -2
  9. prediction_market_agent_tooling/markets/manifold/api.py +16 -4
  10. prediction_market_agent_tooling/markets/manifold/data_models.py +13 -14
  11. prediction_market_agent_tooling/markets/manifold/manifold.py +3 -2
  12. prediction_market_agent_tooling/markets/omen/data_models.py +21 -6
  13. prediction_market_agent_tooling/markets/omen/omen.py +74 -12
  14. prediction_market_agent_tooling/monitor/markets/manifold.py +37 -17
  15. prediction_market_agent_tooling/monitor/markets/omen.py +37 -18
  16. prediction_market_agent_tooling/monitor/markets/polymarket.py +5 -21
  17. prediction_market_agent_tooling/monitor/monitor.py +71 -39
  18. prediction_market_agent_tooling/monitor/monitor_app.py +18 -5
  19. prediction_market_agent_tooling/monitor/monitor_settings.py +27 -0
  20. prediction_market_agent_tooling/tools/gnosis_rpc.py +3 -2
  21. prediction_market_agent_tooling/tools/hexbytes_custom.py +5 -2
  22. prediction_market_agent_tooling/tools/parallelism.py +6 -1
  23. prediction_market_agent_tooling/tools/web3_utils.py +1 -2
  24. {prediction_market_agent_tooling-0.13.1.dist-info → prediction_market_agent_tooling-0.14.0.dist-info}/METADATA +4 -3
  25. {prediction_market_agent_tooling-0.13.1.dist-info → prediction_market_agent_tooling-0.14.0.dist-info}/RECORD +28 -26
  26. {prediction_market_agent_tooling-0.13.1.dist-info → prediction_market_agent_tooling-0.14.0.dist-info}/LICENSE +0 -0
  27. {prediction_market_agent_tooling-0.13.1.dist-info → prediction_market_agent_tooling-0.14.0.dist-info}/WHEEL +0 -0
  28. {prediction_market_agent_tooling-0.13.1.dist-info → prediction_market_agent_tooling-0.14.0.dist-info}/entry_points.txt +0 -0
@@ -55,7 +55,7 @@ class PredictionsCache(BaseModel):
55
55
 
56
56
  def save(self, path: str) -> None:
57
57
  with open(path, "w") as f:
58
- json.dump(self.dict(), f, indent=2)
58
+ json.dump(self.model_dump(), f, indent=2)
59
59
 
60
60
  @staticmethod
61
61
  def load(path: str) -> "PredictionsCache":
@@ -4,6 +4,7 @@ from pydantic.types import SecretStr
4
4
  from pydantic_settings import BaseSettings, SettingsConfigDict
5
5
 
6
6
  from prediction_market_agent_tooling.gtypes import ChecksumAddress, PrivateKey
7
+ from prediction_market_agent_tooling.markets.manifold.api import get_authenticated_user
7
8
  from prediction_market_agent_tooling.tools.utils import check_not_none
8
9
  from prediction_market_agent_tooling.tools.web3_utils import private_key_to_public_key
9
10
 
@@ -30,6 +31,12 @@ class APIKeys(BaseSettings):
30
31
  ENABLE_CACHE: bool = True
31
32
  CACHE_DIR: str = "./.cache"
32
33
 
34
+ @property
35
+ def manifold_user_id(self) -> str:
36
+ return get_authenticated_user(
37
+ api_key=self.manifold_api_key.get_secret_value()
38
+ ).id
39
+
33
40
  @property
34
41
  def manifold_api_key(self) -> SecretStr:
35
42
  return check_not_none(
@@ -122,7 +122,6 @@ def {entrypoint_function_name}(request) -> str:
122
122
 
123
123
  monitor_agent = MARKET_TYPE_TO_DEPLOYED_AGENT[market_type].from_api_keys(
124
124
  name=gcp_fname,
125
- deployableagent_class_name=self.__class__.__name__,
126
125
  start_time=start_time or utcnow(),
127
126
  api_keys=gcp_resolve_api_keys_secrets(api_keys),
128
127
  )
@@ -0,0 +1,152 @@
1
+ from datetime import datetime
2
+ from typing import Any
3
+
4
+ from pydantic import BaseModel
5
+
6
+
7
+ class Metadata(BaseModel):
8
+ creationTimestamp: datetime
9
+ generation: int
10
+ name: str
11
+ namespace: str
12
+ resourceVersion: str
13
+ uid: str
14
+ labels: dict[str, str]
15
+
16
+
17
+ class Metadata1(BaseModel):
18
+ creationTimestamp: datetime | None
19
+ name: str
20
+
21
+
22
+ class Metadata2(BaseModel):
23
+ creationTimestamp: datetime | None
24
+ name: str
25
+
26
+
27
+ class SecretKeyRef(BaseModel):
28
+ key: str
29
+ name: str
30
+ optional: bool
31
+
32
+
33
+ class ValueFrom(BaseModel):
34
+ secretKeyRef: SecretKeyRef
35
+
36
+
37
+ class EnvItem(BaseModel):
38
+ name: str
39
+ valueFrom: ValueFrom
40
+
41
+
42
+ class ConfigMapRef(BaseModel):
43
+ name: str
44
+ optional: bool
45
+
46
+
47
+ class EnvFromItem(BaseModel):
48
+ configMapRef: ConfigMapRef
49
+
50
+
51
+ class Limits(BaseModel):
52
+ cpu: str
53
+ memory: str
54
+
55
+
56
+ class Requests(BaseModel):
57
+ cpu: str
58
+ memory: str
59
+
60
+
61
+ class Resources(BaseModel):
62
+ limits: Limits
63
+ requests: Requests
64
+
65
+
66
+ class Container(BaseModel):
67
+ env: list[EnvItem]
68
+ envFrom: list[EnvFromItem]
69
+ image: str
70
+ imagePullPolicy: str
71
+ name: str
72
+ resources: Resources
73
+ terminationMessagePath: str
74
+ terminationMessagePolicy: str
75
+
76
+
77
+ class NodeSelector(BaseModel):
78
+ role: str
79
+
80
+
81
+ class Toleration(BaseModel):
82
+ effect: str
83
+ key: str
84
+ operator: str
85
+ value: str
86
+
87
+
88
+ class Spec2(BaseModel):
89
+ automountServiceAccountToken: bool
90
+ containers: list[Container]
91
+ dnsPolicy: str
92
+ enableServiceLinks: bool
93
+ nodeSelector: NodeSelector
94
+ restartPolicy: str
95
+ schedulerName: str
96
+ securityContext: dict[str, Any]
97
+ shareProcessNamespace: bool
98
+ terminationGracePeriodSeconds: int
99
+ tolerations: list[Toleration]
100
+
101
+
102
+ class Template(BaseModel):
103
+ metadata: Metadata2
104
+ spec: Spec2
105
+
106
+
107
+ class Spec1(BaseModel):
108
+ activeDeadlineSeconds: int
109
+ backoffLimit: int
110
+ completions: int
111
+ manualSelector: bool
112
+ parallelism: int
113
+ template: Template
114
+
115
+
116
+ class JobTemplate(BaseModel):
117
+ metadata: Metadata1
118
+ spec: Spec1
119
+
120
+
121
+ class Spec(BaseModel):
122
+ concurrencyPolicy: str
123
+ failedJobsHistoryLimit: int
124
+ jobTemplate: JobTemplate
125
+ schedule: str
126
+ successfulJobsHistoryLimit: int
127
+ suspend: bool
128
+ timeZone: str
129
+
130
+
131
+ class Status(BaseModel):
132
+ lastScheduleTime: str | None = None
133
+ lastSuccessfulTime: str | None = None
134
+
135
+
136
+ class KubernetesCronJob(BaseModel):
137
+ apiVersion: str
138
+ kind: str
139
+ metadata: Metadata
140
+ spec: Spec
141
+ status: Status
142
+
143
+
144
+ class Metadata3(BaseModel):
145
+ resourceVersion: str
146
+
147
+
148
+ class KubernetesCronJobsModel(BaseModel):
149
+ apiVersion: str
150
+ items: list[KubernetesCronJob]
151
+ kind: str
152
+ metadata: Metadata3
@@ -1,3 +1,4 @@
1
+ import json
1
2
  import subprocess
2
3
  import sys
3
4
 
@@ -8,6 +9,9 @@ from google.cloud.functions_v2.types.functions import Function
8
9
  from google.cloud.secretmanager import SecretManagerServiceClient
9
10
 
10
11
  from prediction_market_agent_tooling.config import APIKeys
12
+ from prediction_market_agent_tooling.deploy.gcp.kubernetes_models import (
13
+ KubernetesCronJobsModel,
14
+ )
11
15
 
12
16
 
13
17
  def gcloud_deploy_cmd(
@@ -153,6 +157,33 @@ def list_gcp_functions() -> list[Function]:
153
157
  return functions
154
158
 
155
159
 
160
+ def list_gcp_cronjobs(namespace: str) -> KubernetesCronJobsModel:
161
+ return KubernetesCronJobsModel.model_validate_json(
162
+ subprocess.run(
163
+ f"kubectl get cronjobs -o json -n {namespace}",
164
+ shell=True,
165
+ capture_output=True,
166
+ check=True,
167
+ )
168
+ .stdout.decode()
169
+ .strip()
170
+ )
171
+
172
+
173
+ def get_gcp_configmap_data(namespace: str, name: str) -> dict[str, str]:
174
+ data: dict[str, str] = json.loads(
175
+ subprocess.run(
176
+ f"kubectl get configmap {name} -o json -n {namespace}",
177
+ shell=True,
178
+ capture_output=True,
179
+ check=True,
180
+ )
181
+ .stdout.decode()
182
+ .strip()
183
+ )["data"]
184
+ return data
185
+
186
+
156
187
  def get_gcp_function(fname: str) -> Function:
157
188
  response = list_gcp_functions()
158
189
  for function in response:
@@ -1,6 +1,5 @@
1
1
  import typing as t
2
2
  from datetime import datetime
3
- from decimal import Decimal
4
3
  from typing import NewType, Union
5
4
 
6
5
  from eth_typing.evm import ( # noqa: F401 # Import for the sake of easy importing with others from here.
@@ -23,23 +22,22 @@ from prediction_market_agent_tooling.tools.hexbytes_custom import ( # noqa: F40
23
22
  )
24
23
 
25
24
  Wad = Wei # Wei tends to be referred to as `wad` variable in contracts.
26
- USD = NewType(
27
- "USD", Decimal
28
- ) # Decimals are more precise than floats, good for finances.
25
+ USD = NewType("USD", float)
29
26
  PrivateKey = NewType("PrivateKey", SecretStr)
30
- xDai = NewType("xDai", Decimal)
31
- GNO = NewType("GNO", Decimal)
27
+ xDai = NewType("xDai", float)
28
+ GNO = NewType("GNO", float)
32
29
  ABI = NewType("ABI", str)
33
30
  OmenOutcomeToken = NewType("OmenOutcomeToken", int)
31
+ OutcomeStr = NewType("OutcomeStr", str)
34
32
  Probability = NewType("Probability", float)
35
- Mana = NewType("Mana", Decimal) # Manifold's "currency"
36
- USDC = NewType("USDC", Decimal)
33
+ Mana = NewType("Mana", float) # Manifold's "currency"
34
+ USDC = NewType("USDC", float)
37
35
  DatetimeWithTimezone = NewType("DatetimeWithTimezone", datetime)
38
36
  ChainID = NewType("ChainID", int)
39
37
 
40
38
 
41
- def usd_type(amount: Union[str, int, float, Decimal]) -> USD:
42
- return USD(Decimal(amount))
39
+ def usd_type(amount: Union[str, int, float]) -> USD:
40
+ return USD(float(amount))
43
41
 
44
42
 
45
43
  def wei_type(amount: Union[str, int]) -> Wei:
@@ -50,16 +48,16 @@ def omen_outcome_type(amount: Union[str, int, Wei]) -> OmenOutcomeToken:
50
48
  return OmenOutcomeToken(wei_type(amount))
51
49
 
52
50
 
53
- def xdai_type(amount: Union[str, int, float, Decimal]) -> xDai:
54
- return xDai(Decimal(amount))
51
+ def xdai_type(amount: Union[str, int, float]) -> xDai:
52
+ return xDai(float(amount))
55
53
 
56
54
 
57
- def mana_type(amount: Union[str, int, float, Decimal]) -> Mana:
58
- return Mana(Decimal(amount))
55
+ def mana_type(amount: Union[str, int, float]) -> Mana:
56
+ return Mana(float(amount))
59
57
 
60
58
 
61
- def usdc_type(amount: Union[str, int, float, Decimal]) -> USDC:
62
- return USDC(Decimal(amount))
59
+ def usdc_type(amount: Union[str, int, float]) -> USDC:
60
+ return USDC(float(amount))
63
61
 
64
62
 
65
63
  def private_key_type(k: str) -> PrivateKey:
@@ -1,6 +1,5 @@
1
1
  import typing as t
2
2
  from datetime import datetime
3
- from decimal import Decimal
4
3
  from enum import Enum
5
4
 
6
5
  from pydantic import BaseModel, field_validator
@@ -9,6 +8,7 @@ from prediction_market_agent_tooling.gtypes import Probability
9
8
  from prediction_market_agent_tooling.markets.data_models import (
10
9
  BetAmount,
11
10
  Currency,
11
+ Position,
12
12
  Resolution,
13
13
  TokenAmount,
14
14
  )
@@ -45,10 +45,10 @@ class AgentMarket(BaseModel):
45
45
  outcomes: list[str]
46
46
  resolution: Resolution | None
47
47
  created_time: datetime | None
48
- close_time: datetime
48
+ close_time: datetime | None
49
49
  p_yes: Probability
50
50
  url: str
51
- volume: Decimal | None # Should be in currency of `currency` above.
51
+ volume: float | None # Should be in currency of `currency` above.
52
52
 
53
53
  _add_timezone_validator_created_time = field_validator("created_time")(
54
54
  add_utc_timezone_validator
@@ -62,20 +62,20 @@ class AgentMarket(BaseModel):
62
62
  return Probability(1 - self.p_yes)
63
63
 
64
64
  @property
65
- def yes_outcome_price(self) -> Decimal:
65
+ def yes_outcome_price(self) -> float:
66
66
  """
67
67
  Price at prediction market is equal to the probability of given outcome.
68
68
  Keep as an extra property, in case it wouldn't be true for some prediction market platform.
69
69
  """
70
- return Decimal(self.p_yes)
70
+ return self.p_yes
71
71
 
72
72
  @property
73
- def no_outcome_price(self) -> Decimal:
73
+ def no_outcome_price(self) -> float:
74
74
  """
75
75
  Price at prediction market is equal to the probability of given outcome.
76
76
  Keep as an extra property, in case it wouldn't be true for some prediction market platform.
77
77
  """
78
- return Decimal(self.p_no)
78
+ return self.p_no
79
79
 
80
80
  @property
81
81
  def probable_resolution(self) -> Resolution:
@@ -96,7 +96,7 @@ class AgentMarket(BaseModel):
96
96
  return False
97
97
  should_not_happen(f"Market {self.id} does not have a successful resolution.")
98
98
 
99
- def get_bet_amount(self, amount: Decimal) -> BetAmount:
99
+ def get_bet_amount(self, amount: float) -> BetAmount:
100
100
  return BetAmount(amount=amount, currency=self.currency)
101
101
 
102
102
  def get_tiny_bet_amount(self) -> BetAmount:
@@ -153,3 +153,9 @@ class AgentMarket(BaseModel):
153
153
 
154
154
  def get_token_balance(self, user_id: str, outcome: str) -> TokenAmount:
155
155
  raise NotImplementedError("Subclasses must implement this method")
156
+
157
+ def get_positions(self, user_id: str) -> list[Position]:
158
+ """
159
+ Get all non-zero positions a user has in any market.
160
+ """
161
+ raise NotImplementedError("Subclasses must implement this method")
@@ -1,10 +1,11 @@
1
1
  from datetime import datetime
2
- from decimal import Decimal
3
2
  from enum import Enum
4
3
  from typing import TypeAlias
5
4
 
6
5
  from pydantic import BaseModel
7
6
 
7
+ from prediction_market_agent_tooling.gtypes import OutcomeStr
8
+
8
9
 
9
10
  class Currency(str, Enum):
10
11
  xDai = "xDai"
@@ -20,7 +21,7 @@ class Resolution(str, Enum):
20
21
 
21
22
 
22
23
  class TokenAmount(BaseModel):
23
- amount: Decimal
24
+ amount: float
24
25
  currency: Currency
25
26
 
26
27
 
@@ -43,3 +44,15 @@ class ResolvedBet(Bet):
43
44
  @property
44
45
  def is_correct(self) -> bool:
45
46
  return self.outcome == self.market_outcome
47
+
48
+
49
+ class Position(BaseModel):
50
+ market_id: str
51
+ amounts: dict[OutcomeStr, TokenAmount]
52
+
53
+ def __str__(self) -> str:
54
+ amounts_str = ", ".join(
55
+ f"{amount.amount} '{outcome}' tokens"
56
+ for outcome, amount in self.amounts.items()
57
+ )
58
+ return f"Position for market id {self.market_id}: {amounts_str}"
@@ -5,8 +5,7 @@ import requests
5
5
  import tenacity
6
6
  from loguru import logger
7
7
 
8
- from prediction_market_agent_tooling.config import APIKeys
9
- from prediction_market_agent_tooling.gtypes import Mana
8
+ from prediction_market_agent_tooling.gtypes import Mana, SecretStr
10
9
  from prediction_market_agent_tooling.markets.data_models import (
11
10
  BetAmount,
12
11
  Currency,
@@ -107,7 +106,9 @@ def get_one_manifold_binary_market() -> ManifoldMarket:
107
106
  wait=tenacity.wait_fixed(1),
108
107
  after=lambda x: logger.debug(f"place_bet failed, {x.attempt_number=}."),
109
108
  )
110
- def place_bet(amount: Mana, market_id: str, outcome: bool) -> None:
109
+ def place_bet(
110
+ amount: Mana, market_id: str, outcome: bool, manifold_api_key: SecretStr
111
+ ) -> None:
111
112
  outcome_str = "YES" if outcome else "NO"
112
113
  url = f"{MANIFOLD_API_BASE_URL}/v0/bet"
113
114
  params = {
@@ -117,7 +118,7 @@ def place_bet(amount: Mana, market_id: str, outcome: bool) -> None:
117
118
  }
118
119
 
119
120
  headers = {
120
- "Authorization": f"Key {APIKeys().manifold_api_key.get_secret_value()}",
121
+ "Authorization": f"Key {manifold_api_key.get_secret_value()}",
121
122
  "Content-Type": "application/json",
122
123
  }
123
124
  response = requests.post(url, json=params, headers=headers)
@@ -139,12 +140,18 @@ def get_authenticated_user(api_key: str) -> ManifoldUser:
139
140
  headers = {
140
141
  "Authorization": f"Key {api_key}",
141
142
  "Content-Type": "application/json",
143
+ "Cache-Control": "private, no-store, max-age=0",
142
144
  }
143
145
  response = requests.get(url, headers=headers)
144
146
  response.raise_for_status()
145
147
  return ManifoldUser.model_validate(response.json())
146
148
 
147
149
 
150
+ @tenacity.retry(
151
+ stop=tenacity.stop_after_attempt(3),
152
+ wait=tenacity.wait_fixed(1),
153
+ after=lambda x: logger.debug(f"get_manifold_market failed, {x.attempt_number=}."),
154
+ )
148
155
  def get_manifold_market(market_id: str) -> ManifoldMarket:
149
156
  url = f"{MANIFOLD_API_BASE_URL}/v0/market/{market_id}"
150
157
  response = requests.get(url)
@@ -152,6 +159,11 @@ def get_manifold_market(market_id: str) -> ManifoldMarket:
152
159
  return ManifoldMarket.model_validate(response.json())
153
160
 
154
161
 
162
+ @tenacity.retry(
163
+ stop=tenacity.stop_after_attempt(3),
164
+ wait=tenacity.wait_fixed(1),
165
+ after=lambda x: logger.debug(f"get_manifold_bets failed, {x.attempt_number=}."),
166
+ )
155
167
  def get_manifold_bets(
156
168
  user_id: str,
157
169
  start_time: datetime,
@@ -1,6 +1,5 @@
1
1
  import typing as t
2
2
  from datetime import datetime, timedelta
3
- from decimal import Decimal
4
3
 
5
4
  from pydantic import BaseModel, field_validator
6
5
 
@@ -123,17 +122,17 @@ class ManifoldUser(BaseModel):
123
122
  class ManifoldBetFills(BaseModel):
124
123
  amount: Mana
125
124
  matchedBetId: t.Optional[str]
126
- shares: Decimal
125
+ shares: float
127
126
  timestamp: int
128
127
 
129
128
 
130
129
  class ManifoldBetFees(BaseModel):
131
- platformFee: Decimal
132
- liquidityFee: Decimal
133
- creatorFee: Decimal
130
+ platformFee: float
131
+ liquidityFee: float
132
+ creatorFee: float
134
133
 
135
- def get_total(self) -> Decimal:
136
- return Decimal(sum([self.platformFee, self.liquidityFee, self.creatorFee]))
134
+ def get_total(self) -> float:
135
+ return sum([self.platformFee, self.liquidityFee, self.creatorFee])
137
136
 
138
137
 
139
138
  class ManifoldBet(BaseModel):
@@ -141,7 +140,7 @@ class ManifoldBet(BaseModel):
141
140
  https://docs.manifold.markets/api#get-v0bets
142
141
  """
143
142
 
144
- shares: Decimal
143
+ shares: float
145
144
  probBefore: Probability
146
145
  isFilled: t.Optional[bool] = None
147
146
  probAfter: Probability
@@ -187,13 +186,13 @@ class ManifoldContractMetric(BaseModel):
187
186
  hasNoShares: bool
188
187
  hasShares: bool
189
188
  hasYesShares: bool
190
- invested: Decimal
191
- loan: Decimal
189
+ invested: float
190
+ loan: float
192
191
  maxSharesOutcome: t.Optional[str]
193
- payout: Decimal
194
- profit: Decimal
195
- profitPercent: Decimal
196
- totalShares: dict[str, Decimal]
192
+ payout: float
193
+ profit: float
194
+ profitPercent: float
195
+ totalShares: dict[str, float]
197
196
  userId: str
198
197
  userUsername: str
199
198
  userName: str
@@ -1,8 +1,8 @@
1
1
  import typing as t
2
2
  from datetime import datetime
3
- from decimal import Decimal
4
3
  from math import ceil
5
4
 
5
+ from prediction_market_agent_tooling.config import APIKeys
6
6
  from prediction_market_agent_tooling.gtypes import Mana, Probability, mana_type
7
7
  from prediction_market_agent_tooling.markets.agent_market import (
8
8
  AgentMarket,
@@ -32,7 +32,7 @@ class ManifoldAgentMarket(AgentMarket):
32
32
  base_url: t.ClassVar[str] = MANIFOLD_BASE_URL
33
33
 
34
34
  def get_tiny_bet_amount(self) -> BetAmount:
35
- return BetAmount(amount=Decimal(1), currency=self.currency)
35
+ return BetAmount(amount=1, currency=self.currency)
36
36
 
37
37
  def get_minimum_bet_to_win(self, answer: bool, amount_to_win: float) -> Mana:
38
38
  # Manifold lowest bet is 1 Mana, so we need to ceil the result.
@@ -45,6 +45,7 @@ class ManifoldAgentMarket(AgentMarket):
45
45
  amount=Mana(amount.amount),
46
46
  market_id=self.id,
47
47
  outcome=outcome,
48
+ manifold_api_key=APIKeys().manifold_api_key,
48
49
  )
49
50
 
50
51
  @staticmethod
@@ -1,6 +1,5 @@
1
1
  import typing as t
2
2
  from datetime import datetime
3
- from decimal import Decimal
4
3
 
5
4
  from pydantic import BaseModel
6
5
  from web3 import Web3
@@ -90,6 +89,24 @@ class OmenPosition(BaseModel):
90
89
  collateralTokenAddress: HexAddress
91
90
  indexSets: list[int]
92
91
 
92
+ @property
93
+ def condition_id(self) -> HexBytes:
94
+ # I didn't find any example where this wouldn't hold, but keeping this double-check here in case something changes in the future.
95
+ # May be the case if the market is created with multiple oracles.
96
+ if len(self.conditionIds) != 1:
97
+ raise ValueError(
98
+ f"Bug in the logic, please investigate why zero or multiple conditions are returned for position {self.id=}"
99
+ )
100
+ return self.conditionIds[0]
101
+
102
+ @property
103
+ def index_set(self) -> int:
104
+ if len(self.indexSets) != 1:
105
+ raise ValueError(
106
+ f"Bug in the logic, please investigate why zero or multiple index sets are returned for position {self.id=}"
107
+ )
108
+ return self.indexSets[0]
109
+
93
110
  @property
94
111
  def collateral_token_contract_address_checksummed(self) -> ChecksumAddress:
95
112
  return Web3.to_checksum_address(self.collateralTokenAddress)
@@ -133,7 +150,7 @@ class OmenMarket(BaseModel):
133
150
  fee: t.Optional[Wei]
134
151
  resolutionTimestamp: t.Optional[int] = None
135
152
  answerFinalizedTimestamp: t.Optional[int] = None
136
- currentAnswer: t.Optional[str] = None
153
+ currentAnswer: t.Optional[HexBytes] = None
137
154
  creationTimestamp: int
138
155
  condition: Condition
139
156
  question: Question
@@ -156,7 +173,7 @@ class OmenMarket(BaseModel):
156
173
 
157
174
  @property
158
175
  def answer_index(self) -> t.Optional[int]:
159
- return int(self.currentAnswer, 16) if self.currentAnswer else None
176
+ return self.currentAnswer.as_int() if self.currentAnswer else None
160
177
 
161
178
  @property
162
179
  def has_valid_answer(self) -> bool:
@@ -338,9 +355,7 @@ class OmenBet(BaseModel):
338
355
  )
339
356
 
340
357
  return ResolvedBet(
341
- amount=BetAmount(
342
- amount=Decimal(self.collateralAmountUSD), currency=Currency.xDai
343
- ),
358
+ amount=BetAmount(amount=self.collateralAmountUSD, currency=Currency.xDai),
344
359
  outcome=self.boolean_outcome,
345
360
  created_time=self.creation_datetime,
346
361
  market_question=self.title,