pykalshi 0.1.0__py3-none-any.whl → 0.2.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.
- pykalshi/__init__.py +144 -0
- pykalshi/api_keys.py +59 -0
- pykalshi/client.py +526 -0
- pykalshi/enums.py +54 -0
- pykalshi/events.py +87 -0
- pykalshi/exceptions.py +115 -0
- pykalshi/exchange.py +37 -0
- pykalshi/feed.py +592 -0
- pykalshi/markets.py +234 -0
- pykalshi/models.py +552 -0
- pykalshi/orderbook.py +146 -0
- pykalshi/orders.py +144 -0
- pykalshi/portfolio.py +542 -0
- pykalshi/py.typed +0 -0
- pykalshi/rate_limiter.py +171 -0
- {pykalshi-0.1.0.dist-info → pykalshi-0.2.1.dist-info}/METADATA +12 -12
- pykalshi-0.2.1.dist-info/RECORD +35 -0
- pykalshi-0.2.1.dist-info/top_level.txt +1 -0
- pykalshi-0.1.0.dist-info/RECORD +0 -20
- pykalshi-0.1.0.dist-info/top_level.txt +0 -1
- {pykalshi-0.1.0.dist-info → pykalshi-0.2.1.dist-info}/WHEEL +0 -0
- {pykalshi-0.1.0.dist-info → pykalshi-0.2.1.dist-info}/licenses/LICENSE +0 -0
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}>"
|