Skip to content

Commit 5239207

Browse files
committed
docs: improve README and module documentation for crates.io
- Add CI badge, link to official Kalshi API docs, error handling section, and disclaimer to README - Add comprehensive module documentation to error.rs with error categories and handling examples - Expand ws.rs documentation with reconnection pattern, channel reference, and subscription management examples - Update license in Cargo.toml to match README (MIT only)
1 parent 290f0c7 commit 5239207

File tree

4 files changed

+249
-27
lines changed

4 files changed

+249
-27
lines changed

Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ version = "0.1.0"
44
edition = "2024"
55
authors = ["pbeets"]
66
description = "Rust client for the Kalshi trading API and WebSocket streams"
7-
license = "MIT OR Apache-2.0"
7+
license = "MIT"
88
repository = "https://github.com/pbeets/kalshi-trade-rs"
99
keywords = ["kalshi", "trading", "api", "websocket", "async"]
1010
categories = ["api-bindings", "asynchronous"]

README.md

Lines changed: 69 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,18 @@
11
# Rust Kalshi Trading API Client
22

3-
An unofficial Rust client library for connecting to the Kalshi prediction market.
3+
[![Crates.io](https://img.shields.io/crates/v/kalshi-trade-rs.svg)](https://crates.io/crates/kalshi-trade-rs)
4+
[![Documentation](https://docs.rs/kalshi-trade-rs/badge.svg)](https://docs.rs/kalshi-trade-rs)
5+
[![CI](https://github.com/pbeets/kalshi-trade-rs/workflows/CI/badge.svg)](https://github.com/pbeets/kalshi-trade-rs/actions)
6+
[![License](https://img.shields.io/crates/l/kalshi-trade-rs.svg)](https://github.com/pbeets/kalshi-trade-rs#license)
7+
8+
An unofficial Rust client library for the [Kalshi](https://kalshi.com) prediction market, implementing the [Kalshi Trading API v2](https://trading-api.readme.io/reference).
49

510
## Key Features
611

7-
The library provides both REST API and WebSocket streaming capabilities:
12+
This crate provides both REST API and WebSocket streaming capabilities:
813

914
- **REST Client**: Full coverage of the Kalshi API including portfolio management, order operations, market data, and exchange status
1015
- **WebSocket Streaming**: Real-time ticker, trade, orderbook, and fill updates with channel-based message delivery
11-
- **Batch Operations**: Rate-limited batch order creation and cancellation with automatic chunking and retry logic
1216

1317
## Getting Started
1418

@@ -62,7 +66,10 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
6266
let client = KalshiStreamClient::connect(&config).await?;
6367
let mut handle = client.handle();
6468

65-
handle.subscribe(&[Channel::Ticker, Channel::Trade], None).await?;
69+
// Subscribe to channels with market tickers
70+
let markets = &["INXD-25JAN17-B5955", "KXBTC-25DEC31-100000"];
71+
handle.subscribe(Channel::Ticker, markets).await?;
72+
handle.subscribe(Channel::Trade, markets).await?;
6673

6774
loop {
6875
match handle.update_receiver.recv().await {
@@ -89,7 +96,7 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
8996

9097
## Connection and Reconnection
9198

92-
Three connection strategies are available for WebSocket connections:
99+
Two connection strategies are available for WebSocket connections:
93100

94101
- **Simple**: Fast-fail on connection errors
95102
- **Retry**: Exponential backoff with configurable attempts
@@ -102,6 +109,61 @@ use kalshi_trade_rs::ws::ConnectStrategy;
102109
let client = KalshiStreamClient::connect_with_strategy(&config, ConnectStrategy::Retry).await?;
103110
```
104111

105-
## Licensing
112+
## Examples
113+
114+
See the [`examples/`](examples/) directory for working examples covering REST API usage, WebSocket streaming, and reconnection patterns.
115+
116+
```bash
117+
cargo run --example portfolio
118+
```
119+
120+
## Error Handling
121+
122+
The library uses a unified `Error` type for all errors:
123+
124+
```rust
125+
use kalshi_trade_rs::{KalshiClient, Error};
126+
127+
async fn example(client: &KalshiClient) {
128+
match client.get_balance().await {
129+
Ok(balance) => println!("Balance: {} cents", balance.balance),
130+
Err(Error::Auth(msg)) => eprintln!("Auth failed: {}", msg),
131+
Err(Error::Api(msg)) => eprintln!("API error: {}", msg),
132+
Err(Error::Http(e)) => eprintln!("HTTP error: {}", e),
133+
Err(e) => eprintln!("Other error: {}", e),
134+
}
135+
}
136+
```
137+
138+
## Minimum Supported Rust Version
139+
140+
This crate requires **Rust 1.92** or later (uses Rust 2024 edition).
141+
142+
## Running Tests
143+
144+
Tests interact with the real Kalshi API. Set your credentials before running:
145+
146+
```bash
147+
export KALSHI_ENV=demo
148+
export KALSHI_API_KEY_ID=your_api_key_id
149+
export KALSHI_PRIVATE_KEY_PATH=/path/to/your/private_key.pem
150+
151+
cargo test
152+
```
153+
154+
## Contributing
155+
156+
Contributions are welcome! Feel free to open issues or submit pull requests for:
157+
158+
- Bug fixes
159+
- New endpoint coverage
160+
- Documentation improvements
161+
- Additional examples
162+
163+
## Disclaimer
164+
165+
This is an **unofficial** client library and is not affiliated with or endorsed by Kalshi. Use at your own risk. The authors are not responsible for any financial losses incurred through the use of this software. Always test thoroughly with the demo environment before using in production.
166+
167+
## License
106168

107-
This project is licensed under the MIT License.
169+
This project is licensed under the [MIT License](LICENSE).

src/error.rs

Lines changed: 55 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,58 @@
1-
//! Error types and API limit constants.
1+
//! Error types for the Kalshi API client.
2+
//!
3+
//! This module provides the unified [`enum@Error`] type used throughout the crate,
4+
//! along with API limit constants.
5+
//!
6+
//! # Error Categories
7+
//!
8+
//! Errors fall into several categories:
9+
//!
10+
//! - **Network errors**: [`Error::Http`], [`Error::WebSocket`] - connection failures,
11+
//! timeouts, TLS errors
12+
//! - **Authentication errors**: [`Error::Auth`], [`Error::InvalidPrivateKey`] - credential
13+
//! issues, signature failures
14+
//! - **API errors**: [`Error::Api`] - server-side rejections (invalid tickers, insufficient
15+
//! balance, rate limits)
16+
//! - **Validation errors**: [`Error::InvalidPrice`], [`Error::BatchSizeExceeded`], etc. -
17+
//! client-side validation before requests are sent
18+
//! - **Configuration errors**: [`Error::MissingEnvVar`], [`Error::PrivateKeyFileError`] -
19+
//! setup and initialization issues
20+
//!
21+
//! # Example
22+
//!
23+
//! ```no_run
24+
//! use kalshi_trade_rs::{KalshiClient, KalshiConfig, Error};
25+
//!
26+
//! async fn example() -> Result<(), Box<dyn std::error::Error>> {
27+
//! let config = KalshiConfig::from_env()?;
28+
//! let client = KalshiClient::new(config)?;
29+
//!
30+
//! match client.get_balance().await {
31+
//! Ok(balance) => println!("Balance: {} cents", balance.balance),
32+
//! Err(Error::Auth(msg)) => {
33+
//! // Authentication failed - check credentials
34+
//! eprintln!("Authentication error: {}", msg);
35+
//! }
36+
//! Err(Error::Http(e)) => {
37+
//! // Network error - may be transient, consider retry
38+
//! eprintln!("Network error: {}", e);
39+
//! }
40+
//! Err(Error::Api(msg)) => {
41+
//! // Server rejected request - check the message for details
42+
//! eprintln!("API error: {}", msg);
43+
//! }
44+
//! Err(e) => {
45+
//! eprintln!("Other error: {}", e);
46+
//! }
47+
//! }
48+
//! Ok(())
49+
//! }
50+
//! ```
51+
//!
52+
//! # Handling WebSocket Errors
53+
//!
54+
//! For WebSocket connections, errors are delivered via [`crate::ws::StreamMessage::ConnectionLost`]
55+
//! on the update receiver. See the [`ws`](crate::ws) module for reconnection patterns.
256
357
use thiserror::Error;
458

src/ws.rs

Lines changed: 124 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,11 @@
33
//! This module provides a streaming client using the actor pattern for
44
//! real-time market data and account updates.
55
//!
6-
//! # Example
6+
//! # Quick Start
77
//!
88
//! ```no_run
99
//! use kalshi_trade_rs::auth::KalshiConfig;
10-
//! use kalshi_trade_rs::ws::{Channel, ConnectStrategy, KalshiStreamClient};
10+
//! use kalshi_trade_rs::ws::{Channel, ConnectStrategy, KalshiStreamClient, StreamMessage};
1111
//!
1212
//! # async fn example() -> Result<(), Box<dyn std::error::Error>> {
1313
//! let config = KalshiConfig::from_env()?;
@@ -23,32 +23,138 @@
2323
//! // Subscribe to ticker updates for specific markets
2424
//! handle.subscribe(Channel::Ticker, &["INXD-25JAN17-B5955"]).await?;
2525
//!
26-
//! // Add more markets (automatically uses add_markets under the hood)
27-
//! handle.subscribe(Channel::Ticker, &["KXBTC-25DEC31-100000"]).await?;
28-
//!
29-
//! // Check what markets we're subscribed to
30-
//! println!("Ticker markets: {:?}", handle.markets(Channel::Ticker));
31-
//!
32-
//! // Process updates - handle disconnection events
26+
//! // Process updates
3327
//! while let Ok(update) = handle.update_receiver.recv().await {
3428
//! match &update.msg {
35-
//! kalshi_trade_rs::ws::StreamMessage::Closed { reason } => {
36-
//! println!("Connection closed: {}", reason);
37-
//! break;
29+
//! StreamMessage::Ticker(data) => {
30+
//! println!("{}: {}¢", data.market_ticker, data.price);
3831
//! }
39-
//! kalshi_trade_rs::ws::StreamMessage::ConnectionLost { reason } => {
32+
//! StreamMessage::ConnectionLost { reason } => {
4033
//! eprintln!("Connection lost: {}", reason);
41-
//! break; // Reconnect with backoff here
34+
//! break; // Handle reconnection (see below)
4235
//! }
43-
//! _ => println!("Update: {:?}", update),
36+
//! _ => {}
4437
//! }
4538
//! }
39+
//! # Ok(())
40+
//! # }
41+
//! ```
42+
//!
43+
//! # Connection Strategies
44+
//!
45+
//! Two strategies control initial connection behavior:
46+
//!
47+
//! - [`ConnectStrategy::Simple`] - Single attempt, fast-fail on error. Good for testing.
48+
//! - [`ConnectStrategy::Retry`] - Exponential backoff until connected. Recommended for production.
49+
//!
50+
//! **Note**: These strategies only apply to the *initial* connection. Once connected,
51+
//! handling disconnections is your responsibility (see Reconnection Pattern below).
4652
//!
47-
//! // Unsubscribe from specific markets
48-
//! handle.unsubscribe(Channel::Ticker, &["INXD-25JAN17-B5955"]).await?;
53+
//! # Reconnection Pattern
4954
//!
50-
//! // Or unsubscribe from entire channel
55+
//! The library does not automatically reconnect after a connection is lost. This is
56+
//! intentional - reconnection policies vary by application (e.g., max retries, backoff
57+
//! strategy, whether to resubscribe to the same markets).
58+
//!
59+
//! When the connection is lost, you'll receive [`StreamMessage::ConnectionLost`] on
60+
//! your update receiver. Implement reconnection like this:
61+
//!
62+
//! ```no_run
63+
//! use kalshi_trade_rs::auth::KalshiConfig;
64+
//! use kalshi_trade_rs::ws::{Channel, ConnectStrategy, KalshiStreamClient, StreamMessage};
65+
//! use std::time::Duration;
66+
//!
67+
//! async fn run_with_reconnect(config: &KalshiConfig) -> Result<(), Box<dyn std::error::Error>> {
68+
//! let markets = vec!["INXD-25JAN17-B5955", "KXBTC-25DEC31-100000"];
69+
//! let mut attempt = 0;
70+
//!
71+
//! loop {
72+
//! // Connect (Retry strategy handles initial connection retries)
73+
//! let client = match KalshiStreamClient::connect_with_strategy(
74+
//! config,
75+
//! ConnectStrategy::Retry,
76+
//! ).await {
77+
//! Ok(c) => {
78+
//! attempt = 0; // Reset on successful connect
79+
//! c
80+
//! }
81+
//! Err(e) => {
82+
//! eprintln!("Connection failed: {}", e);
83+
//! continue;
84+
//! }
85+
//! };
86+
//!
87+
//! let mut handle = client.handle();
88+
//!
89+
//! // Resubscribe to markets
90+
//! for market in &markets {
91+
//! if let Err(e) = handle.subscribe(Channel::Ticker, &[market]).await {
92+
//! eprintln!("Subscribe failed: {}", e);
93+
//! }
94+
//! }
95+
//!
96+
//! // Process updates until disconnection
97+
//! while let Ok(update) = handle.update_receiver.recv().await {
98+
//! match &update.msg {
99+
//! StreamMessage::Ticker(data) => {
100+
//! println!("{}: {}¢", data.market_ticker, data.price);
101+
//! }
102+
//! StreamMessage::ConnectionLost { reason } => {
103+
//! eprintln!("Connection lost: {}", reason);
104+
//! break; // Exit inner loop to reconnect
105+
//! }
106+
//! StreamMessage::Closed { .. } => {
107+
//! return Ok(()); // Graceful close, don't reconnect
108+
//! }
109+
//! _ => {}
110+
//! }
111+
//! }
112+
//!
113+
//! // Backoff before reconnecting
114+
//! attempt += 1;
115+
//! let backoff = Duration::from_secs(std::cmp::min(attempt * 2, 60));
116+
//! eprintln!("Reconnecting in {:?}...", backoff);
117+
//! tokio::time::sleep(backoff).await;
118+
//! }
119+
//! }
120+
//! ```
121+
//!
122+
//! See the `examples/stream_reconnect.rs` for a complete working example.
123+
//!
124+
//! # Available Channels
125+
//!
126+
//! Market data channels (require market tickers):
127+
//! - [`Channel::Ticker`] - Price and volume updates
128+
//! - [`Channel::Trade`] - Executed trades
129+
//! - [`Channel::OrderbookDelta`] - Orderbook changes
130+
//! - [`Channel::MarketLifecycle`] - Market status changes
131+
//!
132+
//! User channels (no market tickers needed):
133+
//! - [`Channel::Fill`] - Your executed fills
134+
//! - [`Channel::MarketPositions`] - Position changes
135+
//! - [`Channel::Communications`] - RFQ/quote updates
136+
//! - [`Channel::Multivariate`] - Multivariate event updates
137+
//!
138+
//! # Subscription Management
139+
//!
140+
//! ```no_run
141+
//! # use kalshi_trade_rs::ws::{Channel, KalshiStreamHandle};
142+
//! # async fn example(handle: &mut KalshiStreamHandle) -> Result<(), Box<dyn std::error::Error>> {
143+
//! // Subscribe to markets
144+
//! handle.subscribe(Channel::Ticker, &["MARKET-A", "MARKET-B"]).await?;
145+
//!
146+
//! // Add more markets to existing subscription
147+
//! handle.subscribe(Channel::Ticker, &["MARKET-C"]).await?;
148+
//!
149+
//! // Remove specific markets
150+
//! handle.unsubscribe(Channel::Ticker, &["MARKET-A"]).await?;
151+
//!
152+
//! // Unsubscribe from entire channel
51153
//! handle.unsubscribe_all(Channel::Ticker).await?;
154+
//!
155+
//! // Check current subscriptions
156+
//! println!("Markets: {:?}", handle.markets(Channel::Ticker));
157+
//! println!("Subscribed: {}", handle.is_subscribed(Channel::Ticker));
52158
//! # Ok(())
53159
//! # }
54160
//! ```

0 commit comments

Comments
 (0)