Metadata-Version: 2.4
Name: dtr-kalshi
Version: 0.1.3
Summary: Add your description here
Requires-Python: >=3.11
Description-Content-Type: text/markdown
Requires-Dist: cryptography>=46.0.7
Requires-Dist: httpx>=0.28.1
Requires-Dist: websockets>=16.0

# dtr-kalshi

A lightweight Python client for the [Kalshi](https://kalshi.com) Trade API.

## Quickstart

### Installation

```bash
pip install dtr-kalshi
```

### Authentication

Kalshi uses RSA key-pair authentication. You'll need:

1. **API Key ID** — found in your Kalshi account settings.
2. **RSA private key file** — a `.key` file in PEM format, downloaded when you create an API key.

Export both as environment variables so you don't hardcode credentials:

```bash
export KALSHI_API_KEY_ID="your-api-key-id"
export KALSHI_API_KEY_SECRET="/path/to/your/private_key.key"
```

Alternatively, pass them directly to the client constructor.

### Sync usage

`KalshiSyncClient` is a straightforward blocking client suitable for scripts and simple applications.

```python
from dtr_kalshi import KalshiSyncClient

# credentials are read from environment variables by default
client = KalshiSyncClient()

# use use_sandbox=True to hit the demo environment instead
# client = KalshiSyncClient(use_sandbox=True)

# GET /portfolio/balance
balance = client.get('/portfolio/balance')
print(balance)

# POST /portfolio/orders
order = client.post('/portfolio/orders', data={
    'ticker': 'INXD-23DEC31-B4000',
    'side': 'yes',
    'count': 10,
    'type': 'limit',
    'yes_price': 55,
})
print(order)

# DELETE /portfolio/orders/{order_id}
client.delete(f"/portfolio/orders/{order['order']['order_id']}")
```

### Async usage

`KalshiAsyncClient` uses a shared `httpx.AsyncClient` for connection pooling. Use it as an async context manager so the underlying connection is closed cleanly on exit.

```python
import asyncio
from dtr_kalshi import KalshiAsyncClient

async def main():
    async with KalshiAsyncClient() as client:
        balance = await client.get('/portfolio/balance')
        print(balance)

        # fan out multiple requests concurrently
        btc, eth = await asyncio.gather(
            client.get('/markets/KXBTC-25DEC31'),
            client.get('/markets/KXETH-25DEC31'),
        )
        print(btc, eth)

asyncio.run(main())
```

### WebSocket usage

`KalshiWebSocketConnection` streams real-time market data. Use it as an async context manager.

Send commands with `.send_message(cmd, params)`, which maps directly to the Kalshi WebSocket protocol.

#### Callback-based (recommended for multiple channels)

Register handlers with `.on(channel, handler)`, then call `.listen()` to process messages indefinitely.

```python
import asyncio
from dtr_kalshi import KalshiWebSocketConnection

async def main():
    async with KalshiWebSocketConnection() as ws:
        ws.on("ticker", lambda msg: print("ticker:", msg))
        ws.on("trade", lambda msg: print("trade:", msg))

        await ws.send_message("subscribe", {
            "channels": ["ticker", "trade"],
            "market_ticker": ["INXD-23DEC31-B4000"],
        })
        await ws.listen()

asyncio.run(main())
```

#### Iterator-based (for custom message handling)

Iterate directly over the connection with `async for` to handle all messages yourself.

```python
import asyncio
from dtr_kalshi import KalshiWebSocketConnection

async def main():
    async with KalshiWebSocketConnection() as ws:
        await ws.send_message("subscribe", {
            "channels": ["orderbook_delta"],
            "market_ticker": ["INXD-23DEC31-B4000"],
        })
        async for msg in ws:
            print(msg)

asyncio.run(main())
```

#### Concurrent REST and WebSocket

Wrap `.listen()` in a task to run alongside REST calls.

```python
import asyncio
from dtr_kalshi import KalshiAsyncClient, KalshiWebSocketConnection

async def main():
    async with KalshiAsyncClient() as client, KalshiWebSocketConnection() as ws:
        await ws.send_message("subscribe", {
            "channels": ["ticker"],
            "market_ticker": ["INXD-23DEC31-B4000"],
        })
        listen_task = asyncio.create_task(ws.listen())

        balance = await client.get('/portfolio/balance')
        print(balance)

        listen_task.cancel()

asyncio.run(main())
```
