pykalshi 0.1.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.
- kalshi_api/__init__.py +144 -0
- kalshi_api/api_keys.py +59 -0
- kalshi_api/client.py +526 -0
- kalshi_api/enums.py +54 -0
- kalshi_api/events.py +87 -0
- kalshi_api/exceptions.py +115 -0
- kalshi_api/exchange.py +37 -0
- kalshi_api/feed.py +592 -0
- kalshi_api/markets.py +234 -0
- kalshi_api/models.py +552 -0
- kalshi_api/orderbook.py +146 -0
- kalshi_api/orders.py +144 -0
- kalshi_api/portfolio.py +542 -0
- kalshi_api/py.typed +0 -0
- kalshi_api/rate_limiter.py +171 -0
- pykalshi-0.1.0.dist-info/METADATA +182 -0
- pykalshi-0.1.0.dist-info/RECORD +20 -0
- pykalshi-0.1.0.dist-info/WHEEL +5 -0
- pykalshi-0.1.0.dist-info/licenses/LICENSE +21 -0
- pykalshi-0.1.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Rate limiter for proactive request throttling.
|
|
3
|
+
|
|
4
|
+
Composable, optional, and easy to remove. Inject into KalshiClient
|
|
5
|
+
to prevent 429s rather than just retrying after them.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
import threading
|
|
11
|
+
import time
|
|
12
|
+
from collections import deque
|
|
13
|
+
from dataclasses import dataclass, field
|
|
14
|
+
from typing import Protocol
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class RateLimiterProtocol(Protocol):
|
|
18
|
+
"""Protocol for rate limiters. Implement this to create custom limiters."""
|
|
19
|
+
|
|
20
|
+
def acquire(self, weight: int = 1) -> float:
|
|
21
|
+
"""Acquire permission to make a request.
|
|
22
|
+
|
|
23
|
+
Args:
|
|
24
|
+
weight: Cost of this request (default 1).
|
|
25
|
+
|
|
26
|
+
Returns:
|
|
27
|
+
Time waited in seconds (0 if no wait).
|
|
28
|
+
"""
|
|
29
|
+
...
|
|
30
|
+
|
|
31
|
+
def update_from_headers(self, remaining: int | None, reset_at: int | None) -> None:
|
|
32
|
+
"""Update internal state from response headers.
|
|
33
|
+
|
|
34
|
+
Args:
|
|
35
|
+
remaining: Requests remaining in current window.
|
|
36
|
+
reset_at: Unix timestamp when window resets.
|
|
37
|
+
"""
|
|
38
|
+
...
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
@dataclass
|
|
42
|
+
class RateLimiter:
|
|
43
|
+
"""Token bucket rate limiter with sliding window.
|
|
44
|
+
|
|
45
|
+
Thread-safe. Proactively throttles requests to stay under limits.
|
|
46
|
+
|
|
47
|
+
Usage:
|
|
48
|
+
limiter = RateLimiter(requests_per_second=10)
|
|
49
|
+
client = KalshiClient(..., rate_limiter=limiter)
|
|
50
|
+
|
|
51
|
+
# Or manual usage:
|
|
52
|
+
limiter.acquire() # Blocks if needed
|
|
53
|
+
response = make_request()
|
|
54
|
+
limiter.update_from_headers(
|
|
55
|
+
remaining=int(response.headers.get('X-RateLimit-Remaining')),
|
|
56
|
+
reset_at=int(response.headers.get('X-RateLimit-Reset')),
|
|
57
|
+
)
|
|
58
|
+
|
|
59
|
+
Attributes:
|
|
60
|
+
requests_per_second: Target request rate.
|
|
61
|
+
burst: Maximum burst size (default: 2x requests_per_second).
|
|
62
|
+
min_spacing_ms: Minimum ms between requests (anti-burst).
|
|
63
|
+
"""
|
|
64
|
+
|
|
65
|
+
requests_per_second: float = 10.0
|
|
66
|
+
burst: int | None = None
|
|
67
|
+
min_spacing_ms: float = 0.0
|
|
68
|
+
|
|
69
|
+
_timestamps: deque = field(default_factory=deque, repr=False)
|
|
70
|
+
_lock: threading.Lock = field(default_factory=threading.Lock, repr=False)
|
|
71
|
+
_last_request: float = field(default=0.0, repr=False)
|
|
72
|
+
|
|
73
|
+
# Server-reported state (updated from headers)
|
|
74
|
+
_server_remaining: int | None = field(default=None, repr=False)
|
|
75
|
+
_server_reset_at: int | None = field(default=None, repr=False)
|
|
76
|
+
|
|
77
|
+
def __post_init__(self) -> None:
|
|
78
|
+
if self.burst is None:
|
|
79
|
+
self.burst = max(1, int(self.requests_per_second * 2))
|
|
80
|
+
self._window_size = 1.0 # 1 second sliding window
|
|
81
|
+
|
|
82
|
+
def acquire(self, weight: int = 1) -> float:
|
|
83
|
+
"""Block until request is allowed. Returns wait time in seconds."""
|
|
84
|
+
with self._lock:
|
|
85
|
+
now = time.monotonic()
|
|
86
|
+
waited = 0.0
|
|
87
|
+
|
|
88
|
+
# Enforce minimum spacing
|
|
89
|
+
if self.min_spacing_ms > 0:
|
|
90
|
+
elapsed = (now - self._last_request) * 1000
|
|
91
|
+
if elapsed < self.min_spacing_ms:
|
|
92
|
+
sleep_time = (self.min_spacing_ms - elapsed) / 1000
|
|
93
|
+
time.sleep(sleep_time)
|
|
94
|
+
waited += sleep_time
|
|
95
|
+
now = time.monotonic()
|
|
96
|
+
|
|
97
|
+
# Clean old timestamps outside window
|
|
98
|
+
cutoff = now - self._window_size
|
|
99
|
+
while self._timestamps and self._timestamps[0] < cutoff:
|
|
100
|
+
self._timestamps.popleft()
|
|
101
|
+
|
|
102
|
+
# If at capacity, wait for oldest to expire
|
|
103
|
+
while len(self._timestamps) >= self.requests_per_second:
|
|
104
|
+
oldest = self._timestamps[0]
|
|
105
|
+
sleep_time = oldest + self._window_size - now
|
|
106
|
+
if sleep_time > 0:
|
|
107
|
+
time.sleep(sleep_time)
|
|
108
|
+
waited += sleep_time
|
|
109
|
+
now = time.monotonic()
|
|
110
|
+
# Re-clean after sleep
|
|
111
|
+
cutoff = now - self._window_size
|
|
112
|
+
while self._timestamps and self._timestamps[0] < cutoff:
|
|
113
|
+
self._timestamps.popleft()
|
|
114
|
+
|
|
115
|
+
# Check server-reported limits (be conservative)
|
|
116
|
+
if self._server_remaining is not None and self._server_remaining <= 1:
|
|
117
|
+
if self._server_reset_at is not None:
|
|
118
|
+
sleep_until = self._server_reset_at - time.time()
|
|
119
|
+
if sleep_until > 0:
|
|
120
|
+
time.sleep(sleep_until)
|
|
121
|
+
waited += sleep_until
|
|
122
|
+
now = time.monotonic()
|
|
123
|
+
self._server_remaining = None
|
|
124
|
+
|
|
125
|
+
# Record this request
|
|
126
|
+
for _ in range(weight):
|
|
127
|
+
self._timestamps.append(now)
|
|
128
|
+
self._last_request = now
|
|
129
|
+
|
|
130
|
+
return waited
|
|
131
|
+
|
|
132
|
+
def update_from_headers(
|
|
133
|
+
self, remaining: int | None, reset_at: int | None
|
|
134
|
+
) -> None:
|
|
135
|
+
"""Update state from X-RateLimit-Remaining and X-RateLimit-Reset headers."""
|
|
136
|
+
with self._lock:
|
|
137
|
+
if remaining is not None:
|
|
138
|
+
self._server_remaining = remaining
|
|
139
|
+
if reset_at is not None:
|
|
140
|
+
self._server_reset_at = reset_at
|
|
141
|
+
|
|
142
|
+
@property
|
|
143
|
+
def current_rate(self) -> float:
|
|
144
|
+
"""Current request rate (requests in last second)."""
|
|
145
|
+
with self._lock:
|
|
146
|
+
now = time.monotonic()
|
|
147
|
+
cutoff = now - self._window_size
|
|
148
|
+
while self._timestamps and self._timestamps[0] < cutoff:
|
|
149
|
+
self._timestamps.popleft()
|
|
150
|
+
return len(self._timestamps)
|
|
151
|
+
|
|
152
|
+
def reset(self) -> None:
|
|
153
|
+
"""Clear all state. Useful for testing."""
|
|
154
|
+
with self._lock:
|
|
155
|
+
self._timestamps.clear()
|
|
156
|
+
self._last_request = 0.0
|
|
157
|
+
self._server_remaining = None
|
|
158
|
+
self._server_reset_at = None
|
|
159
|
+
|
|
160
|
+
|
|
161
|
+
@dataclass
|
|
162
|
+
class NoOpRateLimiter:
|
|
163
|
+
"""Rate limiter that does nothing. For testing or opt-out."""
|
|
164
|
+
|
|
165
|
+
def acquire(self, weight: int = 1) -> float:
|
|
166
|
+
return 0.0
|
|
167
|
+
|
|
168
|
+
def update_from_headers(
|
|
169
|
+
self, remaining: int | None, reset_at: int | None
|
|
170
|
+
) -> None:
|
|
171
|
+
pass
|
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: pykalshi
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: A typed Python client for the Kalshi prediction markets API with WebSocket streaming, automatic retries, and ergonomic interfaces
|
|
5
|
+
Author-email: Arsh Koneru <arshkon@gmail.com>
|
|
6
|
+
License: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/ArshKA/kalshi-api
|
|
8
|
+
Project-URL: Repository, https://github.com/ArshKA/kalshi-api
|
|
9
|
+
Project-URL: Documentation, https://github.com/ArshKA/kalshi-api#readme
|
|
10
|
+
Project-URL: Issues, https://github.com/ArshKA/kalshi-api/issues
|
|
11
|
+
Keywords: kalshi,prediction-markets,trading,api-client,websocket,finance,betting,forecasting,elections,event-contracts,binary-options,real-time,async,pydantic,orderbook,market-data
|
|
12
|
+
Classifier: Development Status :: 4 - Beta
|
|
13
|
+
Classifier: Intended Audience :: Developers
|
|
14
|
+
Classifier: Intended Audience :: Financial and Insurance Industry
|
|
15
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
16
|
+
Classifier: Operating System :: OS Independent
|
|
17
|
+
Classifier: Programming Language :: Python :: 3
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
21
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
22
|
+
Classifier: Topic :: Office/Business :: Financial :: Investment
|
|
23
|
+
Classifier: Typing :: Typed
|
|
24
|
+
Requires-Python: >=3.9
|
|
25
|
+
Description-Content-Type: text/markdown
|
|
26
|
+
License-File: LICENSE
|
|
27
|
+
Requires-Dist: requests>=2.31.0
|
|
28
|
+
Requires-Dist: pydantic>=2.0.0
|
|
29
|
+
Requires-Dist: cryptography>=41.0.0
|
|
30
|
+
Requires-Dist: python-dotenv>=1.0.0
|
|
31
|
+
Requires-Dist: websockets>=11.0
|
|
32
|
+
Provides-Extra: web
|
|
33
|
+
Requires-Dist: fastapi>=0.100.0; extra == "web"
|
|
34
|
+
Requires-Dist: uvicorn>=0.23.0; extra == "web"
|
|
35
|
+
Requires-Dist: aiofiles>=23.0.0; extra == "web"
|
|
36
|
+
Provides-Extra: dev
|
|
37
|
+
Requires-Dist: pytest>=7.0; extra == "dev"
|
|
38
|
+
Requires-Dist: pytest-cov>=4.0; extra == "dev"
|
|
39
|
+
Requires-Dist: pytest-asyncio>=0.21.0; extra == "dev"
|
|
40
|
+
Requires-Dist: pytest-mock>=3.0.0; extra == "dev"
|
|
41
|
+
Requires-Dist: mypy>=1.0.0; extra == "dev"
|
|
42
|
+
Requires-Dist: types-requests>=2.31.0; extra == "dev"
|
|
43
|
+
Dynamic: license-file
|
|
44
|
+
|
|
45
|
+
# kalshi-api
|
|
46
|
+
|
|
47
|
+
A typed Python client for the [Kalshi](https://kalshi.com) prediction markets API with WebSocket streaming, automatic retries, and ergonomic interfaces.
|
|
48
|
+
|
|
49
|
+
## Installation
|
|
50
|
+
|
|
51
|
+
```bash
|
|
52
|
+
pip install kalshi-api
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
Create a `.env` file with your credentials from [kalshi.com](https://kalshi.com) → Account & Security → API Keys:
|
|
56
|
+
|
|
57
|
+
```
|
|
58
|
+
KALSHI_API_KEY_ID=your-key-id
|
|
59
|
+
KALSHI_PRIVATE_KEY_PATH=/path/to/private-key.key
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
## Quick Start
|
|
63
|
+
|
|
64
|
+
```python
|
|
65
|
+
from kalshi_api import KalshiClient, Action, Side
|
|
66
|
+
|
|
67
|
+
client = KalshiClient()
|
|
68
|
+
user = client.get_user()
|
|
69
|
+
|
|
70
|
+
# Browse markets
|
|
71
|
+
markets = client.get_markets(status="open", limit=5)
|
|
72
|
+
market = client.get_market("KXBTC-25JAN15-B100000")
|
|
73
|
+
|
|
74
|
+
# Place an order
|
|
75
|
+
order = user.place_order(
|
|
76
|
+
market,
|
|
77
|
+
action=Action.BUY,
|
|
78
|
+
side=Side.YES,
|
|
79
|
+
count=10,
|
|
80
|
+
price=45 # cents
|
|
81
|
+
)
|
|
82
|
+
|
|
83
|
+
order.cancel() # if needed
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
## Usage
|
|
87
|
+
|
|
88
|
+
### Portfolio
|
|
89
|
+
|
|
90
|
+
`KalshiClient` handles authentication. Call `get_user()` to access your portfolio:
|
|
91
|
+
|
|
92
|
+
```python
|
|
93
|
+
client = KalshiClient() # Uses .env credentials
|
|
94
|
+
client = KalshiClient(demo=True) # Use demo environment
|
|
95
|
+
|
|
96
|
+
user = client.get_user()
|
|
97
|
+
user.get_balance() # BalanceModel with balance, portfolio_value
|
|
98
|
+
user.get_positions() # Your current positions
|
|
99
|
+
user.get_fills() # Your trade history
|
|
100
|
+
user.get_orders(status="resting") # Your open orders
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
### Markets
|
|
104
|
+
|
|
105
|
+
```python
|
|
106
|
+
# Search markets
|
|
107
|
+
markets = client.get_markets(series_ticker="KXBTC", status="open")
|
|
108
|
+
|
|
109
|
+
# Get a specific market
|
|
110
|
+
market = client.get_market("KXBTC-25JAN15-B100000")
|
|
111
|
+
print(market.title, market.yes_bid, market.yes_ask)
|
|
112
|
+
|
|
113
|
+
# Market data
|
|
114
|
+
orderbook = market.get_orderbook()
|
|
115
|
+
trades = market.get_trades()
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
### Orders
|
|
119
|
+
|
|
120
|
+
```python
|
|
121
|
+
from kalshi_api import Action, Side, OrderType
|
|
122
|
+
|
|
123
|
+
# Limit order (default)
|
|
124
|
+
order = user.place_order(market, Action.BUY, Side.YES, count=10, price=50)
|
|
125
|
+
|
|
126
|
+
# Market order
|
|
127
|
+
order = user.place_order(market, Action.BUY, Side.YES, count=10, order_type=OrderType.MARKET)
|
|
128
|
+
|
|
129
|
+
order.cancel()
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
### Real-time Streaming
|
|
133
|
+
|
|
134
|
+
Subscribe to live market data via WebSocket:
|
|
135
|
+
|
|
136
|
+
```python
|
|
137
|
+
from kalshi_api import Feed
|
|
138
|
+
|
|
139
|
+
async def main():
|
|
140
|
+
async with Feed(client) as feed:
|
|
141
|
+
await feed.subscribe_ticker("KXBTC-25JAN15-B100000")
|
|
142
|
+
await feed.subscribe_orderbook("KXBTC-25JAN15-B100000")
|
|
143
|
+
|
|
144
|
+
async for msg in feed:
|
|
145
|
+
print(msg) # TickerMessage, OrderbookSnapshotMessage, etc.
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
### Error Handling
|
|
149
|
+
|
|
150
|
+
```python
|
|
151
|
+
from kalshi_api import InsufficientFundsError, RateLimitError, KalshiAPIError
|
|
152
|
+
|
|
153
|
+
try:
|
|
154
|
+
user.place_order(...)
|
|
155
|
+
except InsufficientFundsError:
|
|
156
|
+
print("Not enough balance")
|
|
157
|
+
except RateLimitError:
|
|
158
|
+
pass # Client auto-retries with backoff
|
|
159
|
+
except KalshiAPIError as e:
|
|
160
|
+
print(f"{e.status_code}: {e.error_code}")
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
## Comparison with Official SDK
|
|
164
|
+
|
|
165
|
+
| Feature | kalshi-api | kalshi-python (official) |
|
|
166
|
+
|---------|------------|--------------------------|
|
|
167
|
+
| WebSocket streaming | ✓ | — |
|
|
168
|
+
| Automatic retry with backoff | ✓ | — |
|
|
169
|
+
| Rate limit handling | ✓ | — |
|
|
170
|
+
| Domain objects (`Market`, `Order`) | ✓ | — |
|
|
171
|
+
| Typed exceptions | ✓ | — |
|
|
172
|
+
| Local orderbook management | ✓ | — |
|
|
173
|
+
| Pydantic models | ✓ | — |
|
|
174
|
+
| Core trading API coverage | ✓ | ✓ |
|
|
175
|
+
| Full API coverage | — | ✓ |
|
|
176
|
+
|
|
177
|
+
The official SDK is auto-generated from the OpenAPI spec. This library adds the infrastructure needed for production trading: real-time data, error recovery, and ergonomic interfaces.
|
|
178
|
+
|
|
179
|
+
## Links
|
|
180
|
+
|
|
181
|
+
- [Kalshi API Reference](https://trading-api.readme.io/reference)
|
|
182
|
+
- [kalshi-python (official SDK)](https://github.com/Kalshi/kalshi-python)
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
kalshi_api/__init__.py,sha256=H873JDfBga0KEi-G-EJOPBljMZiOTOcpw7x2Qaeru9w,3018
|
|
2
|
+
kalshi_api/api_keys.py,sha256=HMn5bzKo5yX8edcKpJy20NbrR2z7vUa5PJk8TB0iBwU,1922
|
|
3
|
+
kalshi_api/client.py,sha256=4IA-f8OSMFYvxf8tmKL06-2gb7GdDISHDLUPdCtF9ac,19362
|
|
4
|
+
kalshi_api/enums.py,sha256=9YF4ooA1RTprI08-NzqVZoPXISAmHDWgv3j0971BRyI,1127
|
|
5
|
+
kalshi_api/events.py,sha256=0eC9xlqBblwxJdmdiFwjuiL1XUbw6eqcK-vCjoUUgqA,2683
|
|
6
|
+
kalshi_api/exceptions.py,sha256=ikv97ztZwYWKr4SZ0EUTtR50KZQFjbpuI5vMMpPxiW4,3143
|
|
7
|
+
kalshi_api/exchange.py,sha256=39-Zx-VZ6ezkOeB4E9fP4yhTX-qoO4FRSkpcEGmhG60,1359
|
|
8
|
+
kalshi_api/feed.py,sha256=zhPGaER7oBMAHZgpxdppvWDkC-NLn6W_ORnLCkCQh_8,19470
|
|
9
|
+
kalshi_api/markets.py,sha256=ben3yUqpW2Jn1bp67lJ7gyRjUHtwPTZkKQxyH_ao3ZA,6901
|
|
10
|
+
kalshi_api/models.py,sha256=hQdkJMK4sFYXiDEjjEwsEY4skBXEzN3IuJFpzojF8Lw,16038
|
|
11
|
+
kalshi_api/orderbook.py,sha256=40UIqEz-pgM49YdFNTXAd63Rw2hCaEq-sQPPy9l3q_E,4830
|
|
12
|
+
kalshi_api/orders.py,sha256=F4mk0PPS_cBzDRbg0x50GpQb-AqfJPES2Vw45Zes3Rc,3726
|
|
13
|
+
kalshi_api/portfolio.py,sha256=JgkgGTSEytn3OrIRbdci-bvkBOtRmxBK69uFuyYwArI,19937
|
|
14
|
+
kalshi_api/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
15
|
+
kalshi_api/rate_limiter.py,sha256=UJPjl-YUtLdVwQcaoq97ZH1oeS-IhqtV8K5G6WpUyDg,5901
|
|
16
|
+
pykalshi-0.1.0.dist-info/licenses/LICENSE,sha256=dUhuoK-TCRQMpuLEAdfme-qPSJI0TlcH9jlNxeg9_EQ,1056
|
|
17
|
+
pykalshi-0.1.0.dist-info/METADATA,sha256=qYNSSrd3cUElCZt-FuL0MoSifw7NdKoQlx6lpjcfYRo,5603
|
|
18
|
+
pykalshi-0.1.0.dist-info/WHEEL,sha256=wUyA8OaulRlbfwMtmQsvNngGrxQHAvkKcvRmdizlJi0,92
|
|
19
|
+
pykalshi-0.1.0.dist-info/top_level.txt,sha256=j1cmqf7vUVoke4KfEg_he913wTsgN5nT0Ky5P3P7Dik,11
|
|
20
|
+
pykalshi-0.1.0.dist-info/RECORD,,
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2024
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
kalshi_api
|