pykalshi 0.1.0__py3-none-any.whl → 0.2.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.
pykalshi/markets.py ADDED
@@ -0,0 +1,234 @@
1
+ from __future__ import annotations
2
+
3
+ import logging
4
+ from typing import TYPE_CHECKING
5
+
6
+ from .models import MarketModel, CandlestickResponse, OrderbookResponse, SeriesModel, TradeModel
7
+ from .enums import CandlestickPeriod, MarketStatus
8
+
9
+ if TYPE_CHECKING:
10
+ from .client import KalshiClient
11
+ from .events import Event
12
+
13
+ logger = logging.getLogger(__name__)
14
+
15
+
16
+ class Market:
17
+ """Represents a Kalshi Market.
18
+
19
+ Key fields are exposed as typed properties for IDE support.
20
+ All other MarketModel fields are accessible via attribute delegation.
21
+ """
22
+
23
+ def __init__(self, client: KalshiClient, data: MarketModel) -> None:
24
+ self._client = client
25
+ self.data = data
26
+
27
+ # --- Typed properties for core fields ---
28
+
29
+ @property
30
+ def ticker(self) -> str:
31
+ return self.data.ticker
32
+
33
+ @property
34
+ def event_ticker(self) -> str | None:
35
+ return self.data.event_ticker
36
+
37
+ @property
38
+ def status(self) -> MarketStatus | None:
39
+ return self.data.status
40
+
41
+ @property
42
+ def title(self) -> str | None:
43
+ return self.data.title
44
+
45
+ @property
46
+ def subtitle(self) -> str | None:
47
+ return self.data.subtitle
48
+
49
+ @property
50
+ def yes_bid(self) -> int | None:
51
+ return self.data.yes_bid
52
+
53
+ @property
54
+ def yes_ask(self) -> int | None:
55
+ return self.data.yes_ask
56
+
57
+ @property
58
+ def no_bid(self) -> int | None:
59
+ return self.data.no_bid
60
+
61
+ @property
62
+ def no_ask(self) -> int | None:
63
+ return self.data.no_ask
64
+
65
+ @property
66
+ def last_price(self) -> int | None:
67
+ return self.data.last_price
68
+
69
+ @property
70
+ def volume(self) -> int | None:
71
+ return self.data.volume
72
+
73
+ @property
74
+ def volume_24h(self) -> int | None:
75
+ return self.data.volume_24h
76
+
77
+ @property
78
+ def open_interest(self) -> int | None:
79
+ return self.data.open_interest
80
+
81
+ @property
82
+ def liquidity(self) -> int | None:
83
+ return self.data.liquidity
84
+
85
+ @property
86
+ def open_time(self) -> str | None:
87
+ return self.data.open_time
88
+
89
+ @property
90
+ def close_time(self) -> str | None:
91
+ return self.data.close_time
92
+
93
+ @property
94
+ def result(self) -> str | None:
95
+ return self.data.result
96
+
97
+ @property
98
+ def series_ticker(self) -> str | None:
99
+ """Series ticker if available in the market data."""
100
+ return self.data.series_ticker
101
+
102
+ def resolve_series_ticker(self) -> str | None:
103
+ """Fetch series_ticker from the event API if not present in market data.
104
+
105
+ Makes an API call to look up the series via the event. Use this when
106
+ you need the series_ticker but it wasn't included in the market response.
107
+
108
+ Returns:
109
+ The series ticker, or None if it couldn't be resolved.
110
+ """
111
+ if self.data.series_ticker is not None:
112
+ return self.data.series_ticker
113
+ if not self.data.event_ticker:
114
+ return None
115
+ try:
116
+ event_response = self._client.get(f"/events/{self.data.event_ticker}")
117
+ return event_response["event"]["series_ticker"]
118
+ except Exception as e:
119
+ logger.warning(
120
+ "Failed to resolve series_ticker for %s: %s", self.data.ticker, e
121
+ )
122
+ return None
123
+
124
+ def get_orderbook(self) -> OrderbookResponse:
125
+ """Get the orderbook for this market."""
126
+ response = self._client.get(f"/markets/{self.data.ticker}/orderbook")
127
+ return OrderbookResponse.model_validate(response)
128
+
129
+ def get_candlesticks(
130
+ self,
131
+ start_ts: int,
132
+ end_ts: int,
133
+ period: CandlestickPeriod = CandlestickPeriod.ONE_HOUR,
134
+ ) -> CandlestickResponse:
135
+ """Get candlestick data for this market.
136
+
137
+ Args:
138
+ start_ts: Start timestamp (Unix seconds).
139
+ end_ts: End timestamp (Unix seconds).
140
+ period: Candlestick period (ONE_MINUTE, ONE_HOUR, or ONE_DAY).
141
+ """
142
+ series = self.resolve_series_ticker()
143
+ if not series:
144
+ raise ValueError(f"Market {self.data.ticker} does not have a series_ticker.")
145
+
146
+ query = f"start_ts={start_ts}&end_ts={end_ts}&period_interval={period.value}"
147
+ endpoint = f"/series/{series}/markets/{self.data.ticker}/candlesticks?{query}"
148
+ response = self._client.get(endpoint)
149
+ return CandlestickResponse.model_validate(response)
150
+
151
+ def get_trades(
152
+ self,
153
+ min_ts: int | None = None,
154
+ max_ts: int | None = None,
155
+ limit: int = 100,
156
+ cursor: str | None = None,
157
+ fetch_all: bool = False,
158
+ ) -> list[TradeModel]:
159
+ """Get public trade history for this market.
160
+
161
+ Args:
162
+ min_ts: Minimum timestamp (Unix seconds).
163
+ max_ts: Maximum timestamp (Unix seconds).
164
+ limit: Maximum trades per page (default 100).
165
+ cursor: Pagination cursor for fetching next page.
166
+ fetch_all: If True, automatically fetch all pages.
167
+ """
168
+ return self._client.get_trades(
169
+ ticker=self.ticker,
170
+ min_ts=min_ts,
171
+ max_ts=max_ts,
172
+ limit=limit,
173
+ cursor=cursor,
174
+ fetch_all=fetch_all,
175
+ )
176
+
177
+ def get_event(self) -> Event | None:
178
+ """Get the parent Event for this market.
179
+
180
+ Returns:
181
+ The Event object, or None if event_ticker is not set.
182
+ """
183
+ if not self.event_ticker:
184
+ return None
185
+ return self._client.get_event(self.event_ticker)
186
+
187
+ def __getattr__(self, name: str):
188
+ # Fallback for fields without explicit properties.
189
+ return getattr(self.data, name)
190
+
191
+ def __eq__(self, other: object) -> bool:
192
+ if not isinstance(other, Market):
193
+ return NotImplemented
194
+ return self.data.ticker == other.data.ticker
195
+
196
+ def __hash__(self) -> int:
197
+ return hash(self.data.ticker)
198
+
199
+ def __repr__(self) -> str:
200
+ return f"<Market {self.data.ticker}>"
201
+
202
+
203
+ class Series:
204
+ """Represents a Kalshi Series (collection of related markets)."""
205
+
206
+ def __init__(self, client: KalshiClient, data: SeriesModel) -> None:
207
+ self._client = client
208
+ self.data = data
209
+
210
+ @property
211
+ def ticker(self) -> str:
212
+ return self.data.ticker
213
+
214
+ @property
215
+ def title(self) -> str | None:
216
+ return self.data.title
217
+
218
+ @property
219
+ def category(self) -> str | None:
220
+ return self.data.category
221
+
222
+ def get_markets(self, **kwargs) -> list[Market]:
223
+ """Get all markets in this series."""
224
+ return self._client.get_markets(series_ticker=self.ticker, **kwargs)
225
+
226
+ def get_events(self, **kwargs) -> list[Event]:
227
+ """Get all events in this series."""
228
+ return self._client.get_events(series_ticker=self.ticker, **kwargs)
229
+
230
+ def __getattr__(self, name: str):
231
+ return getattr(self.data, name)
232
+
233
+ def __repr__(self) -> str:
234
+ return f"<Series {self.ticker}>"