git:20260205.c8ee35f to git:20260210.cd0fc2e

65 added, 586 removed. Audit A to A.

---
name: trading-kalshi
- description: "Execute trades on Kalshi - full REST API access for markets, orders, positions, balance"
- emoji: "📈"
- gates:
- envs:
- - KALSHI_EMAIL
- - KALSHI_PASSWORD
+ description: "Kalshi trading - search markets, place orders, stream prices, advanced order types"
+ commands:
+ - /kalshi
+ - /trading-kalshi
---
- # Kalshi Trading - Complete API Reference
-
- Full access to Kalshi's CFTC-regulated prediction market via their REST API.
+ # Kalshi Trading
- **Docs**: https://docs.kalshi.com/welcome
- **Discord**: #dev channel for support
+ Full access to Kalshi prediction markets: search, trade, stream real-time data, and use advanced order types (TWAP, bracket, trigger).
## Required Environment Variables
```bash
- KALSHI_EMAIL=your@email.com
- KALSHI_PASSWORD=your_password
- ```
-
- ## Installation
-
- ```bash
- pip install requests
- # Optional: pip install kalshi-python # Official SDK
- ```
-
- ---
-
- ## API Base URLs
-
- ```python
- # Production
- BASE_URL = "https://trading-api.kalshi.com/trade-api/v2"
-
- # Demo/Sandbox (for testing)
- DEMO_URL = "https://demo-api.kalshi.co/trade-api/v2"
- ```
-
- ---
-
- ## Authentication
-
- Kalshi uses email/password login returning a bearer token valid for 30 minutes.
-
- ### Login & Token Management
-
- ```python
- import os
- import time
- import requests
-
- BASE_URL = "https://trading-api.kalshi.com/trade-api/v2"
-
- class KalshiClient:
- def __init__(self):
- self.email = os.getenv("KALSHI_EMAIL")
- self.password = os.getenv("KALSHI_PASSWORD")
- self.token = None
- self.token_expiry = 0
- self.member_id = None
-
- def _ensure_auth(self):
- """Refresh token if expired (30 min lifetime)"""
- if time.time() > self.token_expiry - 60:
- self._login()
-
- def _login(self):
- """POST /login - Get new auth token"""
- r = requests.post(f"{BASE_URL}/login", json={
- "email": self.email,
- "password": self.password
- })
- r.raise_for_status()
- data = r.json()
- self.token = data["token"]
- self.member_id = data.get("member_id")
- self.token_expiry = time.time() + 29 * 60 # Refresh at 29 mins
- return data
-
- def _headers(self):
- """Get auth headers for requests"""
- self._ensure_auth()
- return {
- "Authorization": f"Bearer {self.token}",
- "Content-Type": "application/json"
- }
-
- def logout(self):
- """POST /logout - Invalidate current token"""
- r = requests.post(f"{BASE_URL}/logout", headers=self._headers())
- self.token = None
- self.token_expiry = 0
- return r.status_code == 200
-
- # Initialize
- client = KalshiClient()
- ```
-
- ---
-
- ## Market Data Endpoints
-
- ### Get Markets
-
- ```python
- def get_markets(
- status: str = "open", # "open", "closed", "settled"
- series_ticker: str = None, # Filter by series
- limit: int = 100,
- cursor: str = None # For pagination
- ):
- """GET /markets - List markets"""
- params = {"status": status, "limit": limit}
- if series_ticker:
- params["series_ticker"] = series_ticker
- if cursor:
- params["cursor"] = cursor
-
- r = requests.get(f"{BASE_URL}/markets", headers=client._headers(), params=params)
- r.raise_for_status()
- data = r.json()
- return {
- "markets": data.get("markets", []),
- "cursor": data.get("cursor") # Use for pagination
- }
-
- # Examples
- markets = get_markets(series_ticker="INXD") # S&P 500 daily
- markets = get_markets(series_ticker="FED") # Fed rate decisions
- markets = get_markets(series_ticker="KXBTC") # Bitcoin price
- ```
-
- ### Get Single Market
-
- ```python
- def get_market(ticker: str):
- """GET /markets/{ticker} - Single market details"""
- r = requests.get(f"{BASE_URL}/markets/{ticker}", headers=client._headers())
- r.raise_for_status()
- return r.json()["market"]
-
- market = get_market("INXD-24JAN10-T5805")
- # Returns: ticker, title, subtitle, status, yes_bid, yes_ask,
- # no_bid, no_ask, volume, open_interest, close_time, result
- ```
-
- ### Get Market Orderbook
-
- ```python
- def get_orderbook(ticker: str, depth: int = 10):
- """GET /markets/{ticker}/orderbook - Full orderbook"""
- r = requests.get(f"{BASE_URL}/markets/{ticker}/orderbook",
- headers=client._headers(),
- params={"depth": depth})
- r.raise_for_status()
- data = r.json()["orderbook"]
-
- # data["yes"] = list of [price, size] for YES side
- # data["no"] = list of [price, size] for NO side
- return data
-
- book = get_orderbook("INXD-24JAN10-T5805")
- print(f"Yes bids: {book['yes']}") # [[45, 100], [44, 200], ...]
- print(f"No asks: {book['no']}")
- ```
-
- ### Get Market History/Trades
-
- ```python
- def get_market_history(ticker: str, limit: int = 100):
- """GET /markets/{ticker}/history - Trade history"""
- r = requests.get(f"{BASE_URL}/markets/{ticker}/history",
- headers=client._headers(),
- params={"limit": limit})
- r.raise_for_status()
- return r.json().get("history", [])
-
- trades = get_market_history("INXD-24JAN10-T5805")
- for t in trades:
- print(f"{t['created_time']}: {t['count']} @ {t['yes_price']}¢")
- ```
-
- ### Get Series/Events
-
- ```python
- def get_series():
- """GET /series - List all series (categories)"""
- r = requests.get(f"{BASE_URL}/series", headers=client._headers())
- r.raise_for_status()
- return r.json().get("series", [])
-
- def get_events(series_ticker: str = None):
- """GET /events - List events"""
- params = {}
- if series_ticker:
- params["series_ticker"] = series_ticker
- r = requests.get(f"{BASE_URL}/events", headers=client._headers(), params=params)
- r.raise_for_status()
- return r.json().get("events", [])
-
- series = get_series()
- events = get_events("FED")
+ KALSHI_API_KEY_ID=your_api_key_id
+ KALSHI_PRIVATE_KEY=your_private_key_pem_contents
+ # OR
+ KALSHI_PRIVATE_KEY_PATH=/path/to/private_key.pem
```
- ---
-
- ## Order Management
-
- ### Place Order
-
- ```python
- def place_order(
- ticker: str,
- side: str, # "yes" or "no"
- action: str, # "buy" or "sell"
- count: int, # Number of contracts
- price: int = None, # Price in cents (1-99), None for market
- order_type: str = "limit", # "limit" or "market"
- expiration_ts: int = None, # Optional: GTD expiration timestamp
- client_order_id: str = None # Optional: Your reference ID
- ):
- """POST /portfolio/orders - Place an order"""
- payload = {
- "ticker": ticker,
- "side": side.lower(),
- "action": action.lower(),
- "count": count,
- "type": order_type
- }
-
- if order_type == "limit" and price:
- # yes_price is always from YES perspective
- payload["yes_price"] = price if side.lower() == "yes" else (100 - price)
-
- if expiration_ts:
- payload["expiration_ts"] = expiration_ts
-
- if client_order_id:
- payload["client_order_id"] = client_order_id
-
- r = requests.post(f"{BASE_URL}/portfolio/orders",
- headers=client._headers(),
- json=payload)
- r.raise_for_status()
- return r.json()
-
- # Examples
- # Buy 10 YES at 45 cents
- result = place_order("INXD-24JAN10-T5805", "yes", "buy", 10, 45)
+ Authentication uses RSA key-pair signing (not email/password). Generate an API key from the Kalshi dashboard.
- # Sell 5 NO at 30 cents (equivalent to YES at 70 cents)
- result = place_order("INXD-24JAN10-T5805", "no", "sell", 5, 30)
+ Optional:
- # Market order (immediate fill)
- result = place_order("INXD-24JAN10-T5805", "yes", "buy", 10, order_type="market")
+ ```bash
+ DRY_RUN=true # Simulate trades without executing
```
- ### Batch Create Orders
+ ## Commands
- ```python
- def batch_create_orders(orders: list):
- """POST /portfolio/orders/batched - Create multiple orders"""
- payload = {"orders": orders}
- r = requests.post(f"{BASE_URL}/portfolio/orders/batched",
- headers=client._headers(),
- json=payload)
- r.raise_for_status()
- return r.json()
+ ### Market Data
- orders = [
- {"ticker": "INXD-24JAN10-T5805", "side": "yes", "action": "buy", "count": 5, "type": "limit", "yes_price": 40},
- {"ticker": "INXD-24JAN10-T5805", "side": "yes", "action": "buy", "count": 5, "type": "limit", "yes_price": 42},
- ]
- results = batch_create_orders(orders)
```
-
- ### Amend Order
-
- ```python
- def amend_order(order_id: str, count: int = None, price: int = None):
- """POST /portfolio/orders/{order_id}/amend - Modify order"""
- payload = {}
- if count:
- payload["count"] = count
- if price:
- payload["yes_price"] = price
-
- r = requests.post(f"{BASE_URL}/portfolio/orders/{order_id}/amend",
- headers=client._headers(),
- json=payload)
- r.raise_for_status()
- return r.json()
+ /kalshi search <query> - Search markets
+ /kalshi market <ticker> - Market details
+ /kalshi book <ticker> - View orderbook (REST snapshot)
+ /kalshi events [query] - Browse events
+ /kalshi event <event-ticker> - Event details + markets
```
- ### Decrease Order Size
+ ### Trading
- ```python
- def decrease_order(order_id: str, reduce_by: int):
- """POST /portfolio/orders/{order_id}/decrease - Reduce order size"""
- r = requests.post(f"{BASE_URL}/portfolio/orders/{order_id}/decrease",
- headers=client._headers(),
- json={"reduce_by": reduce_by})
- r.raise_for_status()
- return r.json()
```
-
- ### Cancel Order
-
- ```python
- def cancel_order(order_id: str):
- """DELETE /portfolio/orders/{order_id} - Cancel single order"""
- r = requests.delete(f"{BASE_URL}/portfolio/orders/{order_id}",
- headers=client._headers())
- return r.status_code in [200, 204]
-
- def batch_cancel_orders(order_ids: list):
- """DELETE /portfolio/orders/batched - Cancel multiple orders"""
- r = requests.delete(f"{BASE_URL}/portfolio/orders/batched",
- headers=client._headers(),
- json={"order_ids": order_ids})
- r.raise_for_status()
- return r.json()
-
- # Cancel specific order
- cancel_order("abc123-order-id")
-
- # Cancel multiple
- batch_cancel_orders(["order-1", "order-2", "order-3"])
+ /kalshi buy <ticker> <contracts> <price> - Buy YES contracts
+ /kalshi sell <ticker> <contracts> <price> - Sell YES contracts
+ /kalshi orders - View open orders
+ /kalshi cancel <order-id> - Cancel an order
+ /kalshi cancel all - Cancel all orders
+ /kalshi balance - Account balance
```
- ### Get Orders
-
- ```python
- def get_orders(
- ticker: str = None,
- status: str = None, # "resting", "canceled", "executed", "pending"
- limit: int = 100
- ):
- """GET /portfolio/orders - List orders"""
- params = {"limit": limit}
- if ticker:
- params["ticker"] = ticker
- if status:
- params["status"] = status
-
- r = requests.get(f"{BASE_URL}/portfolio/orders",
- headers=client._headers(),
- params=params)
- r.raise_for_status()
- return r.json().get("orders", [])
-
- def get_order(order_id: str):
- """GET /portfolio/orders/{order_id} - Single order"""
- r = requests.get(f"{BASE_URL}/portfolio/orders/{order_id}",
- headers=client._headers())
- r.raise_for_status()
- return r.json()["order"]
+ ### Advanced Orders
- # Get all open orders
- orders = get_orders(status="resting")
- for o in orders:
- print(f"{o['order_id']}: {o['action']} {o['side']} {o['remaining_count']} @ {o['yes_price']}¢")
```
-
- ---
-
- ## Portfolio Management
-
- ### Get Balance
-
- ```python
- def get_balance():
- """GET /portfolio/balance - Account balance"""
- r = requests.get(f"{BASE_URL}/portfolio/balance", headers=client._headers())
- r.raise_for_status()
- data = r.json()
+ /kalshi twap <buy|sell> <ticker> <total> <price> [slices] [interval-sec]
+ /kalshi twap status - Active TWAP progress
+ /kalshi twap cancel <id> - Cancel TWAP
- return {
- "balance": data.get("balance", 0) / 100, # Available in dollars
- "portfolio_value": data.get("portfolio_value", 0) / 100
- }
+ /kalshi bracket <ticker> <size> <tp> <sl> - Set take-profit / stop-loss
+ /kalshi bracket status - Active brackets
+ /kalshi bracket cancel <id> - Cancel bracket
- bal = get_balance()
- print(f"Available: ${bal['balance']:.2f}")
- print(f"Portfolio: ${bal['portfolio_value']:.2f}")
+ /kalshi trigger buy <ticker> <size> <price> [limit] - Buy when price drops
+ /kalshi trigger sell <ticker> <size> <price> [limit] - Sell when price rises
+ /kalshi trigger list - Active triggers
+ /kalshi trigger cancel <id> - Cancel trigger
```
- ### Get Positions
-
- ```python
- def get_positions(limit: int = 100):
- """GET /portfolio/positions - Current positions"""
- r = requests.get(f"{BASE_URL}/portfolio/positions",
- headers=client._headers(),
- params={"limit": limit})
- r.raise_for_status()
- return r.json().get("market_positions", [])
+ ### Real-Time Streaming (WebSocket)
- positions = get_positions()
- for p in positions:
- if p.get("position", 0) != 0:
- print(f"{p['ticker']}: {p['position']} contracts @ avg {p['average_price']}¢")
- print(f" Realized P&L: ${p.get('realized_pnl', 0) / 100:.2f}")
```
-
- ### Get Fills (Trade History)
-
- ```python
- def get_fills(
- ticker: str = None,
- limit: int = 100,
- cursor: str = None
- ):
- """GET /portfolio/fills - Executed trades"""
- params = {"limit": limit}
- if ticker:
- params["ticker"] = ticker
- if cursor:
- params["cursor"] = cursor
-
- r = requests.get(f"{BASE_URL}/portfolio/fills",
- headers=client._headers(),
- params=params)
- r.raise_for_status()
- data = r.json()
- return {
- "fills": data.get("fills", []),
- "cursor": data.get("cursor")
- }
-
- fills = get_fills()
- for f in fills["fills"]:
- print(f"{f['created_time']}: {f['action']} {f['side']} {f['count']} @ {f['price']}¢")
+ /kalshi stream <ticker> [channels] - Start streaming (ticker,trade,orderbook)
+ /kalshi stream-fills - Stream your order fills
+ /kalshi streams - List active streams
+ /kalshi unstream <ticker> - Stop streaming a market
+ /kalshi unstream-fills - Stop fill notifications
+ /kalshi realtime-book <ticker> - Get real-time orderbook from stream
```
- ### Get Settlements
-
- ```python
- def get_settlements(limit: int = 100):
- """GET /portfolio/settlements - Settlement history"""
- r = requests.get(f"{BASE_URL}/portfolio/settlements",
- headers=client._headers(),
- params={"limit": limit})
- r.raise_for_status()
- return r.json().get("settlements", [])
+ ### Cross-Platform
- settlements = get_settlements()
- for s in settlements:
- print(f"{s['ticker']}: Settled at {s['settlement_value']}¢, P&L: ${s['revenue'] / 100:.2f}")
```
-
- ---
-
- ## Exchange Status
-
- ```python
- def get_exchange_status():
- """GET /exchange/status - Exchange operational status"""
- r = requests.get(f"{BASE_URL}/exchange/status", headers=client._headers())
- r.raise_for_status()
- return r.json()
-
- status = get_exchange_status()
- print(f"Trading: {status.get('trading_active')}")
- print(f"Exchange open: {status.get('exchange_active')}")
+ /kalshi route <ticker> <buy|sell> <size> - Compare prices across platforms
+ /kalshi circuit - Circuit breaker status
```
- ---
-
- ## WebSocket (Real-time Data)
-
- For real-time updates, use WebSocket after REST authentication:
-
- ```python
- import websocket
- import json
-
- def on_message(ws, message):
- data = json.loads(message)
- print(f"Update: {data}")
-
- def on_open(ws):
- # Subscribe to orderbook updates
- ws.send(json.dumps({
- "type": "subscribe",
- "channel": "orderbook",
- "ticker": "INXD-24JAN10-T5805"
- }))
+ ## Examples
- # Connect with auth token
- ws = websocket.WebSocketApp(
- f"wss://trading-api.kalshi.com/trade-api/ws/v2?token={client.token}",
- on_message=on_message,
- on_open=on_open
- )
- ws.run_forever()
```
-
- ---
-
- ## Complete Trading Bot
-
- ```python
- #!/usr/bin/env python3
- """
- Production Kalshi trading bot
- """
-
- import os
- import time
- import requests
-
- BASE_URL = "https://trading-api.kalshi.com/trade-api/v2"
-
- class KalshiBot:
- def __init__(self):
- self.email = os.getenv("KALSHI_EMAIL")
- self.password = os.getenv("KALSHI_PASSWORD")
- self.token = None
- self.token_expiry = 0
-
- def _auth(self):
- if time.time() > self.token_expiry - 60:
- r = requests.post(f"{BASE_URL}/login", json={
- "email": self.email, "password": self.password
- })
- r.raise_for_status()
- self.token = r.json()["token"]
- self.token_expiry = time.time() + 29 * 60
-
- def _h(self):
- self._auth()
- return {"Authorization": f"Bearer {self.token}", "Content-Type": "application/json"}
-
- def get_market(self, ticker):
- r = requests.get(f"{BASE_URL}/markets/{ticker}", headers=self._h())
- r.raise_for_status()
- return r.json()["market"]
-
- def get_positions(self):
- r = requests.get(f"{BASE_URL}/portfolio/positions", headers=self._h())
- r.raise_for_status()
- return {p["ticker"]: p for p in r.json().get("market_positions", [])}
-
- def get_balance(self):
- r = requests.get(f"{BASE_URL}/portfolio/balance", headers=self._h())
- r.raise_for_status()
- return r.json().get("balance", 0) / 100
-
- def buy(self, ticker, side, count, price):
- payload = {
- "ticker": ticker, "side": side, "action": "buy",
- "count": count, "type": "limit",
- "yes_price": price if side == "yes" else (100 - price)
- }
- r = requests.post(f"{BASE_URL}/portfolio/orders", headers=self._h(), json=payload)
- return r.json() if r.status_code == 200 else {"error": r.text}
-
- def sell(self, ticker, side, count, price):
- payload = {
- "ticker": ticker, "side": side, "action": "sell",
- "count": count, "type": "limit",
- "yes_price": price if side == "yes" else (100 - price)
- }
- r = requests.post(f"{BASE_URL}/portfolio/orders", headers=self._h(), json=payload)
- return r.json() if r.status_code == 200 else {"error": r.text}
-
- # Run
- bot = KalshiBot()
- TICKER = "INXD-24JAN10-T5805"
-
- while True:
- try:
- market = bot.get_market(TICKER)
- positions = bot.get_positions()
- balance = bot.get_balance()
-
- yes_bid = market["yes_bid"]
- yes_ask = market["yes_ask"]
- pos = positions.get(TICKER, {}).get("position", 0)
-
- print(f"Balance: ${balance:.2f}, Position: {pos}, Price: {yes_bid}/{yes_ask}")
-
- # Trading logic
- if yes_ask < 40 and pos < 10 and balance > 5:
- print(f"BUYING at {yes_ask}")
- bot.buy(TICKER, "yes", 5, yes_ask)
- elif yes_bid > 60 and pos > 0:
- print(f"SELLING at {yes_bid}")
- bot.sell(TICKER, "yes", pos, yes_bid)
-
- time.sleep(10)
-
- except Exception as e:
- print(f"Error: {e}")
- time.sleep(30)
+ /kalshi search bitcoin
+ /kalshi market KXBTC-24JAN01
+ /kalshi book KXBTC-24JAN01
+ /kalshi buy KXBTC-24JAN01 10 0.65
+ /kalshi sell KXBTC-24JAN01 5 0.70
+ /kalshi stream KXBTC-24JAN01 ticker,trade
+ /kalshi twap buy KXBTC-24JAN01 50 0.60 10 30
+ /kalshi bracket KXBTC-24JAN01 10 0.80 0.40
+ /kalshi trigger buy KXBTC-24JAN01 10 45
```
- ---
-
- ## Popular Market Series
-
- | Series | Description | Example Ticker |
- |--------|-------------|----------------|
- | FED | Fed rate decisions | FED-24MAR-T525 |
- | INXD | S&P 500 daily close | INXD-24JAN10-T5805 |
- | KXBTC | Bitcoin price brackets | KXBTC-24JAN-T45000 |
- | KXETH | Ethereum price | KXETH-24JAN-T2500 |
- | CPI | Inflation data | CPI-24JAN-T3.5 |
- | GDP | GDP growth | GDP-24Q1-T2.0 |
- | NFP | Non-farm payrolls | NFP-24JAN-T200K |
-
- ---
-
- ## Key Notes
-
- 1. **Prices in CENTS** - 45 means $0.45 per contract
- 2. **Contracts pay $1 if correct** - Cost is the price, profit is $1 - price
- 3. **No trading fees** - Only spread matters
- 4. **Token expires in 30 min** - Auto-refresh before expiry
- 5. **US residents only** - KYC verification required
- 6. **Rate limits exist** - Implement exponential backoff on 429 errors
- 7. **Max position limits** - Varies by market, check market details
+ ## Notes
+ - Prices are in decimal format for commands (0.65 = 65 cents)
+ - Trigger prices are in cents (45 = 45 cents)
+ - Contracts pay $1 if correct; cost is the price
+ - Circuit breaker integration blocks trades when risk limits are hit
+ - Trigger orders poll every 5 seconds