| Crates.io | kalshi-rust |
| lib.rs | kalshi-rust |
| version | 1.0.0 |
| created_at | 2025-12-26 21:44:38.465464+00 |
| updated_at | 2025-12-26 21:44:38.465464+00 |
| description | An async Rust wrapper for the Kalshi trading API with full HTTPS and WebSocket support for building prediction market trading bots. |
| homepage | |
| repository | https://github.com/whrit/kalshi-rust |
| max_upload_size | |
| id | 2006306 |
| size | 426,382 |
A comprehensive async Rust wrapper for the Kalshi trading API, providing both HTTPS and WebSocket support for building high-performance trading bots on the Kalshi prediction markets platform.
tokio for high-performance concurrent operationsAdd kalshi to your Cargo.toml:
[dependencies]
kalshi = "0.9"
tokio = { version = "1", features = ["full"] }
.pem format)use kalshi::{Kalshi, TradingEnvironment};
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
// Initialize the client with key-based authentication
let kalshi = Kalshi::new(
TradingEnvironment::DemoMode,
"your-key-id",
"path/to/private.pem"
).await?;
// Get exchange status
let status = kalshi.get_exchange_status().await?;
println!("Exchange is open: {}", status.trading_active);
// Fetch markets
let (cursor, markets) = kalshi.get_markets(
Some(10), // limit
None, // cursor
None, // event_ticker
None, // series_ticker
Some("open".to_string()), // status
None, None, None, None, None, None, None, None
).await?;
println!("Found {} markets", markets.len());
Ok(())
}
kalshi-rust uses RSA-PSS key-based authentication for secure API access. Authentication is handled automatically when creating a Kalshi instance.
// Demo mode (paper trading - recommended for testing)
let kalshi_demo = Kalshi::new(
TradingEnvironment::DemoMode,
key_id,
pem_path
).await?;
// Production mode (real money trading)
let kalshi_prod = Kalshi::new(
TradingEnvironment::ProdMode,
key_id,
pem_path
).await?;
Store your credentials securely using environment variables:
use std::env;
let key_id = env::var("KALSHI_API_KEY")?;
let pem_path = env::var("KALSHI_PEM_PATH")?;
let kalshi = Kalshi::new(
TradingEnvironment::DemoMode,
&key_id,
&pem_path
).await?;
use kalshi::{Action, Side, OrderType};
// Create a limit order to buy 10 "Yes" contracts at 55 cents
let order = kalshi.create_order(
Action::Buy,
None, // client_order_id (auto-generated if None)
10, // count
Side::Yes,
"MARKET-TICKER-2024".to_string(),
OrderType::Limit,
None, // buy_max_cost
None, // expiration_ts
Some(55), // yes_price (cents)
None, // no_price
None, // sell_position_floor
None, // yes_price_dollars
None, // no_price_dollars
None, // time_in_force
None, // post_only
None, // reduce_only
None, // self_trade_prevention_type
None, // order_group_id
None, // cancel_order_on_pause
).await?;
println!("Order ID: {}", order.order_id);
use kalshi::OrderCreationField;
let orders = vec![
OrderCreationField {
action: Action::Buy,
count: 5,
side: Side::Yes,
ticker: "MARKET1-2024".to_string(),
input_type: OrderType::Limit,
yes_price: Some(50),
client_order_id: None,
// ... other fields
},
OrderCreationField {
action: Action::Buy,
count: 10,
side: Side::No,
ticker: "MARKET2-2024".to_string(),
input_type: OrderType::Limit,
no_price: Some(45),
client_order_id: None,
// ... other fields
},
];
let results = kalshi.batch_create_order(orders).await?;
for (i, result) in results.iter().enumerate() {
match result {
Ok(order) => println!("Order {} created: {}", i, order.order_id),
Err(e) => println!("Order {} failed: {}", i, e),
}
}
use kalshi::{Side, Action};
// Change the price of an existing order
let response = kalshi.amend_order(
"order-uuid",
"MARKET-TICKER",
Side::Yes,
Action::Buy,
"original-client-id",
"updated-client-id",
Some(60), // new yes_price
None, // no_price
None, // yes_price_dollars
None, // no_price_dollars
Some(15), // new count
).await?;
println!("Old order: {:?}", response.old_order);
println!("New order: {:?}", response.order);
// Cancel a single order
let (order, reduced_by) = kalshi.cancel_order("order-id").await?;
println!("Cancelled order, reduced by {} contracts", reduced_by);
// Batch cancel orders
let order_ids = vec!["order-1".to_string(), "order-2".to_string()];
let results = kalshi.batch_cancel_order(order_ids).await?;
// Get account balance
let balance = kalshi.get_balance().await?;
println!("Balance: {} cents", balance);
// Get positions
let (cursor, event_positions, market_positions) = kalshi.get_positions(
None, None, None, None, None, None
).await?;
for position in market_positions {
println!("Market: {}, Position: {}, PnL: {}",
position.ticker, position.position, position.realized_pnl);
}
// Get fills
let (cursor, fills) = kalshi.get_fills(
Some("MARKET-TICKER".to_string()),
None, None, None, Some(100), None
).await?;
// Get settlements
let (cursor, settlements) = kalshi.get_settlements(
Some(100), None, None, None, None, None
).await?;
// Get all open markets
let (cursor, markets) = kalshi.get_markets(
Some(20), // limit
None, // cursor
None, // event_ticker
None, // series_ticker
Some("open".to_string()), // status
None, // tickers
None, None, None, None, None, None, None
).await?;
for market in markets {
println!("{}: {} - Last price: {}",
market.ticker, market.title, market.last_price);
}
// Get a specific market
let market = kalshi.get_market("MARKET-TICKER-2024").await?;
println!("Market: {}", market.title);
// Get orderbook with depth limit
let orderbook = kalshi.get_orderbook("MARKET-TICKER", Some(10)).await?;
// Best bid/ask in cents
if let Some(yes_bids) = orderbook.yes {
if let Some(best_bid) = yes_bids.first() {
println!("Best Yes bid: {} cents, size: {}", best_bid[0], best_bid[1]);
}
}
// Dollar prices (as tuples of (f32, i32))
for (price, size) in &orderbook.yes_dollars {
println!("Yes: ${:.4} x {}", price, size);
}
use chrono::Utc;
let now = Utc::now().timestamp();
let one_day_ago = now - 86400;
// Single market candlesticks
let candlesticks = kalshi.get_market_candlesticks(
"MARKET-TICKER",
"SERIES-TICKER",
Some(one_day_ago),
Some(now),
Some(60) // 60-minute candles
).await?;
for candle in candlesticks {
println!("Time: {}, Yes Close: {}, Volume: {}",
candle.end_ts, candle.yes_close, candle.volume);
}
// Batch candlestick retrieval for multiple markets
let tickers = vec!["MARKET-1".to_string(), "MARKET-2".to_string()];
let batch_data = kalshi.batch_get_market_candlesticks(
tickers,
one_day_ago,
now,
60,
None
).await?;
for market_data in batch_data {
println!("Market: {}, Candles: {}",
market_data.ticker, market_data.candlesticks.len());
}
// Get recent trades for a market
let (cursor, trades) = kalshi.get_trades(
Some(100),
None,
Some("MARKET-TICKER".to_string()),
None,
None
).await?;
for trade in trades {
println!("Trade: {} contracts at {} cents",
trade.count, trade.yes_price);
}
// Get series
let (cursor, series_list) = kalshi.get_series_list(
Some(20),
None,
Some("politics".to_string()),
None
).await?;
// Get specific series
let series = kalshi.get_series("SERIES-TICKER").await?;
// Get events
let (cursor, events) = kalshi.get_multiple_events(
Some(10),
None,
None,
None,
None
).await?;
Connect to the WebSocket API for real-time market data and trading updates.
use kalshi::Channel;
use futures_util::StreamExt;
// Create WebSocket client from existing Kalshi instance
let mut ws = kalshi.websocket();
// Connect to WebSocket
ws.connect().await?;
// Subscribe to orderbook updates for a market
let sub = ws.subscribe(Channel::Orderbook {
ticker: "MARKET-TICKER".to_string(),
}).await?;
println!("Subscribed with SID: {}", sub.sid);
// Listen for messages
while let Some(msg_result) = ws.next_message().await {
match msg_result {
Ok(msg) => {
println!("Received: {:?}", msg);
}
Err(e) => {
eprintln!("WebSocket error: {}", e);
break;
}
}
}
// Unsubscribe and disconnect
ws.unsubscribe(sub.sid).await?;
ws.disconnect().await?;
use kalshi::Channel;
// Market-specific channels
let orderbook_channel = Channel::Orderbook {
ticker: "MARKET-TICKER".to_string(),
};
let ticker_channel = Channel::Ticker {
ticker: "MARKET-TICKER".to_string(),
};
let trade_channel = Channel::Trade {
ticker: "MARKET-TICKER".to_string(),
};
// Portfolio channels
let fill_channel = Channel::Fill;
let order_channel = Channel::OrderUpdate;
// Subscribe to a channel
let subscription = ws.subscribe(orderbook_channel).await?;
// Get exchange status
let status = kalshi.get_exchange_status().await?;
println!("Trading active: {}", status.trading_active);
println!("Exchange active: {}", status.exchange_active);
// Get exchange schedule
let schedule = kalshi.get_exchange_schedule().await?;
println!("Standard hours: {:?}", schedule.standard_hours);
kalshi-rust provides comprehensive error handling through the KalshiError enum:
use kalshi::KalshiError;
match kalshi.create_order(/* ... */).await {
Ok(order) => {
println!("Order created: {}", order.order_id);
}
Err(e) => match e {
KalshiError::RequestError(req_err) => {
eprintln!("Request failed: {}", req_err);
}
KalshiError::UserInputError(msg) => {
eprintln!("Invalid input: {}", msg);
}
KalshiError::Auth(msg) => {
eprintln!("Authentication error: {}", msg);
}
KalshiError::InternalError(msg) => {
eprintln!("Internal error: {}", msg);
}
}
}
RequestError: HTTP/network errors, serialization failures, client/server errorsUserInputError: Invalid parameters or input validation failuresAuth: Authentication and authorization errorsInternalError: Unexpected internal errors (please report these!)| Module | Description |
|---|---|
portfolio |
Orders, positions, fills, settlements, balance management |
market |
Markets, orderbooks, trades, series, candlesticks |
events |
Event data and multi-event queries |
exchange |
Exchange status and trading schedule |
websocket |
Real-time WebSocket data streaming |
api_keys |
API key management |
collection |
Collection-related endpoints |
search |
Search functionality |
live_data |
Live milestone data feeds |
// Trading environment
pub enum TradingEnvironment {
DemoMode, // Paper trading
ProdMode, // Real money
}
// Order parameters
pub enum Action { Buy, Sell }
pub enum Side { Yes, No }
pub enum OrderType { Market, Limit }
pub enum OrderStatus { Resting, Canceled, Executed, Pending }
// Advanced order parameters
pub enum TimeInForce {
FillOrKill,
GoodTillCanceled,
ImmediateOrCancel,
}
pub enum SelfTradePreventionType {
TakerAtCross,
Maker,
}
For complete API documentation, run:
cargo doc --open
Or visit the online documentation.
The library includes comprehensive tests. To run them:
cd kalshi
cargo test
For authenticated tests, set up environment variables:
export KALSHI_DEMO_API_KEY=your-key-id
export KALSHI_DEMO_PEM_PATH=/path/to/private.pem
export KALSHI_TEST_ENV=demo
cargo test
See TESTING.md for detailed testing instructions.
Complete examples are available in the sample_bot directory.
use kalshi::{Kalshi, TradingEnvironment};
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let kalshi = Kalshi::new(
TradingEnvironment::DemoMode,
&std::env::var("KALSHI_API_KEY")?,
&std::env::var("KALSHI_PEM_PATH")?
).await?;
let (_, markets) = kalshi.get_markets(
Some(50),
None,
None,
None,
Some("open".to_string()),
None, None, None, None, None, None, None, None
).await?;
println!("Top markets by volume:");
let mut sorted_markets = markets;
sorted_markets.sort_by(|a, b| b.volume_24h.cmp(&a.volume_24h));
for market in sorted_markets.iter().take(10) {
println!("{}: {} - Volume: {}, Last: {}",
market.ticker,
market.title,
market.volume_24h,
market.last_price
);
}
Ok(())
}
Contributions are welcome! Please:
cargo fmt and cargo clippyThis project follows standard Rust conventions:
cargo fmt before committingcargo clippy passes with no warningsThis project is dual-licensed under:
You may choose either license for your use.
This library is not officially affiliated with Kalshi. Use at your own risk. Trading involves financial risk. Always test with demo mode before trading with real money.
Developed by the Rust trading community. Special thanks to all contributors who have helped improve this library.
If you find this library useful, please consider giving it a star on GitHub!