Polymarket API Rust Tutorial: High-Performance Trading Bot Guide
Build high-performance Polymarket trading bots in Rust. Rust offers exceptional performance for HFT bots, market makers, and trading algorithms. This comprehensive 2026 guide covers HTTP clients, WebSocket connections, async patterns, error handling, and performance optimization with production-ready code examples from real trading bots.
Rust's zero-cost abstractions, memory safety guarantees, and excellent async runtime make it ideal for building production Polymarket bots. Whether you're building arbitrage bots, market makers, or data aggregators, this guide provides verified patterns from production codebases. High search volume: "polymarket rust", "rust trading bot" - developers increasingly search for Rust implementations. Professional developers leverage analytics platforms like PolyTrack Pro to access pre-built trading infrastructure and skip months of low-level API integration work.
🔑 Why Rust for Polymarket Bots?
- • Performance: Zero-cost abstractions, no garbage collector, excellent async runtime
- • Memory Safety: Prevents common bugs (use-after-free, data races) at compile time
- • Async Runtime: tokio provides excellent async/await support for concurrent operations
- • Type Safety: Strong type system catches errors at compile time
- • Production Ready: Used by major trading firms for HFT systems
Project Setup and Dependencies
Cargo.toml Configuration
Add these dependencies to your Cargo.toml:
[dependencies]
# Async runtime
tokio = { version = "1.35", features = ["full"] }
# HTTP client
reqwest = { version = "0.11", features = ["json", "rustls-tls"], default-features = false }
# WebSocket
tokio-tungstenite = { version = "0.21", features = ["rustls-tls-native-roots"] }
futures-util = "0.3"
# JSON serialization
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
# Decimal math (avoid floating point errors)
rust_decimal = { version = "1.33", features = ["serde"] }
rust_decimal_macros = "1.33"
# Error handling
anyhow = "1.0"
thiserror = "1.0"
# Time handling
chrono = { version = "0.4", features = ["serde"] }
# Logging
tracing = "0.1"
tracing-subscriber = { version = "0.3", features = ["env-filter"] }
[profile.release]
opt-level = 3
lto = true
codegen-units = 1
panic = "abort"HTTP Client Setup
Basic HTTP Client with TCP_NODELAY
Use reqwest::Client with TCP_NODELAY enabled for efficient HTTP requests:
use reqwest::Client;
use anyhow::{Context, Result};
pub struct MarketMonitor {
client: Client,
gamma_url: String,
}
impl MarketMonitor {
pub fn new(gamma_url: String) -> Self {
// Enable TCP_NODELAY for efficient network usage (disables Nagle's algorithm)
let client = Client::builder()
.tcp_nodelay(true)
.build()
.expect("Failed to create HTTP client");
Self { client, gamma_url }
}
pub async fn fetch_market_by_slug(&self, slug: &str) -> Result<Vec<serde_json::Value>> {
let url = format!("{}/events?slug={}", self.gamma_url, slug);
let response = self.client
.get(&url)
.send()
.await
.context("Failed to fetch event")?;
let events: Vec<serde_json::Value> = response
.json()
.await
.context("Failed to parse JSON")?;
Ok(events)
}
}Type-Safe Struct Definitions
Define structs with serde for type-safe API responses:
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct Event {
pub id: String,
pub slug: String,
pub title: String,
pub markets: Vec<Market>,
pub active: bool,
pub closed: bool,
}
#[derive(Debug, Clone, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct Market {
pub condition_id: String,
pub question: String,
#[serde(default)]
pub tokens: Vec<Token>,
pub outcome_prices: Option<String>, // JSON string like "["0.5", "0.5"]"
pub outcomes: Option<String>, // JSON string like "["Up", "Down"]"
pub clob_token_ids: Option<String>, // JSON string
pub end_date_iso: Option<String>,
pub active: bool,
pub closed: bool,
}
#[derive(Debug, Clone, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct Token {
pub token_id: String,
pub outcome: String,
}
// Parse JSON strings (API returns arrays as JSON strings)
impl Market {
pub fn outcome_prices_parsed(&self) -> anyhow::Result<Vec<String>> {
match &self.outcome_prices {
Some(s) => serde_json::from_str(s).context("Failed to parse outcome_prices"),
None => Ok(vec![]),
}
}
pub fn outcomes_parsed(&self) -> anyhow::Result<Vec<String>> {
match &self.outcomes {
Some(s) => serde_json::from_str(s).context("Failed to parse outcomes"),
None => Ok(vec![]),
}
}
}See What Whales Are Trading Right Now
Get instant alerts when top traders make moves. Track P&L, win rates, and copy winning strategies.

Free forever. No credit card required.
Fetching Market Data
Fetch Markets with Error Handling
use anyhow::{Context, Result};
use reqwest::Client;
pub async fn fetch_active_markets(client: &Client) -> Result<Vec<Market>> {
let url = "https://gamma-api.polymarket.com/markets?active=true&_limit=100";
let response = client
.get(url)
.send()
.await
.context("Failed to send HTTP request")?;
let markets: Vec<Market> = response
.json()
.await
.context("Failed to parse markets JSON")?;
Ok(markets)
}
// Search with slug_contains filter
pub async fn search_markets_by_slug(
client: &Client,
slug_contains: &str
) -> Result<Vec<Event>> {
let url = format!(
"https://gamma-api.polymarket.com/events?slug_contains={}&active=true&closed=false&_limit=5",
slug_contains
);
let response = client
.get(&url)
.send()
.await
.context("Failed to search markets")?;
let events: Vec<Event> = response
.json()
.await
.context("Failed to parse events JSON")?;
Ok(events)
}WebSocket Connections
Connect to Polymarket WebSocket
Polymarket uses separate WebSocket endpoints for market data (public) and user data (authenticated):
- •
wss://ws-subscriptions-clob.polymarket.com/ws/market- Market data (orderbooks, prices) - •
wss://ws-subscriptions-clob.polymarket.com/ws/user- User data (orders, trades) - requires auth
use tokio_tungstenite::{connect_async, tungstenite::Message};
use futures_util::{SinkExt, StreamExt};
use std::time::Duration;
use anyhow::{Context, Result};
use serde_json::json;
pub async fn connect_to_market_websocket(
token_ids: &[String]
) -> Result<()> {
let url = "wss://ws-subscriptions-clob.polymarket.com/ws/market";
// Connect with timeout
let connect_future = connect_async(url);
let (ws_stream, _) = tokio::time::timeout(
Duration::from_secs(10),
connect_future
)
.await
.context("WebSocket connection timeout")?
.context("Failed to connect to WebSocket")?;
let (mut write, mut read) = ws_stream.split();
// Subscribe to market channel
// Format: {"assets_ids": ["token_id_1", "token_id_2"], "type": "market"}
let market_subscribe = json!({
"assets_ids": token_ids,
"type": "market"
});
write
.send(Message::Text(market_subscribe.to_string()))
.await
.context("Failed to subscribe to market")?;
// Keep-alive ping task (every 10 seconds)
let ping_write = tokio::sync::Mutex::new(write);
tokio::spawn(async move {
let mut interval = tokio::time::interval(Duration::from_secs(10));
loop {
interval.tick().await;
if let Ok(mut writer) = ping_write.lock() {
let _ = writer.send(Message::Ping(vec![])).await;
}
}
});
// Read messages
while let Some(msg) = read.next().await {
match msg {
Ok(Message::Text(text)) => {
// Parse orderbook updates, price changes, etc.
if let Ok(data) = serde_json::from_str::<serde_json::Value>(&text) {
println!("Received: {:?}", data);
}
}
Ok(Message::Pong(_)) => {
// Pong received
}
Ok(Message::Close(_)) => {
break;
}
Err(e) => {
eprintln!("WebSocket error: {}", e);
break;
}
_ => {}
}
}
Ok(())
}Handling WebSocket Reconnections
Implement automatic reconnection with exponential backoff:
use tokio::time::{sleep, Duration};
use std::sync::Arc;
pub struct WebSocketManager {
reconnect_count: Arc<std::sync::RwLock<u32>>,
max_reconnect_delay: Duration,
}
impl WebSocketManager {
pub async fn run_with_reconnect(&self, token_ids: Vec<String>) {
loop {
match self.connect_to_market_websocket(&token_ids).await {
Ok(_) => {
// Connection closed normally
*self.reconnect_count.write().unwrap() = 0;
}
Err(e) => {
eprintln!("WebSocket error: {}", e);
let count = *self.reconnect_count.read().unwrap();
let delay = std::cmp::min(
Duration::from_secs(2_u64.pow(count)),
self.max_reconnect_delay
);
eprintln!("Reconnecting in {:?}...", delay);
sleep(delay).await;
*self.reconnect_count.write().unwrap() = count + 1;
}
}
}
}
}Performance Optimization
Performance Best Practices
- • TCP_NODELAY: Enable for efficient network usage (already shown in HTTP client setup)
- • Connection Pooling: Reuse HTTP client connections with
reqwest::Client - • Parallel Operations: Use
tokio::join!for concurrent HTTP/WebSocket operations - • Efficient Data Handling: Use type-safe structs with serde for reliable JSON parsing
Parallel Market Fetching Example
pub async fn fetch_multiple_markets_parallel(
client: &Client,
slugs: &[String]
) -> Result<Vec<Event>> {
let mut tasks = Vec::new();
for slug in slugs {
let client = client.clone();
let slug = slug.clone();
tasks.push(tokio::spawn(async move {
let url = format!("https://gamma-api.polymarket.com/events?slug={}", slug);
client.get(&url)
.send()
.await
.and_then(|r| r.json::<Vec<Event>>())
.await
}));
}
let results: Vec<_> = futures::future::join_all(tasks).await;
let mut events = Vec::new();
for result in results {
if let Ok(Ok(mut event_vec)) = result {
events.append(&mut event_vec);
}
}
Ok(events)
}Error Handling Patterns
Using anyhow and thiserror
use anyhow::{Context, Result};
use thiserror::Error;
#[derive(Error, Debug)]
pub enum PolymarketError {
#[error("HTTP request failed: {0}")]
HttpError(#[from] reqwest::Error),
#[error("JSON parsing failed: {0}")]
JsonError(#[from] serde_json::Error),
#[error("WebSocket error: {0}")]
WebSocketError(String),
#[error("Market not found: {0}")]
MarketNotFound(String),
}
// Usage with context
pub async fn fetch_market_safe(client: &Client, slug: &str) -> Result<Event> {
let url = format!("https://gamma-api.polymarket.com/events?slug={}", slug);
let response = client
.get(&url)
.send()
.await
.context("Failed to send HTTP request")?;
let events: Vec<Event> = response
.json()
.await
.context("Failed to parse JSON response")?;
events.into_iter()
.next()
.ok_or_else(|| anyhow::anyhow!("Market not found: {}", slug))
}Complete Example: Market Monitor
use anyhow::{Context, Result};
use chrono::{DateTime, Utc};
use reqwest::Client;
use std::time::Duration;
use tokio::time::sleep;
pub struct MarketMonitor {
client: Client,
gamma_url: String,
}
impl MarketMonitor {
pub fn new(gamma_url: String) -> Self {
let client = Client::builder()
.tcp_nodelay(true)
.timeout(Duration::from_secs(10))
.build()
.expect("Failed to create HTTP client");
Self { client, gamma_url }
}
pub async fn find_active_market(&self, slug_contains: &str) -> Result<Option<Event>> {
let url = format!(
"{}/events?slug_contains={}&active=true&closed=false&_limit=5",
self.gamma_url, slug_contains
);
let response = self.client
.get(&url)
.send()
.await
.context("Failed to search markets")?;
let events: Vec<Event> = response
.json()
.await
.context("Failed to parse events")?;
// Find first event with active markets
for event in events {
if !event.markets.is_empty() {
let market = &event.markets[0];
if let Some(end_date) = &market.end_date_iso {
if let Ok(end_time) = DateTime::parse_from_rfc3339(end_date) {
if end_time.with_timezone(&Utc) > Utc::now() {
return Ok(Some(event));
}
}
}
}
}
Ok(None)
}
pub async fn monitor_loop(&self, slug_contains: &str) {
loop {
match self.find_active_market(slug_contains).await {
Ok(Some(event)) => {
println!("Found active market: {}", event.title);
// Process market...
}
Ok(None) => {
println!("No active market found");
}
Err(e) => {
eprintln!("Error: {}", e);
}
}
sleep(Duration::from_secs(5)).await;
}
}
}
#[tokio::main]
async fn main() -> Result<()> {
let monitor = MarketMonitor::new(
"https://gamma-api.polymarket.com".to_string()
);
monitor.monitor_loop("btc-updown-15m").await;
Ok(())
}Best Practices
- • Use Decimal Types: Avoid floating-point for prices - use
rust_decimal::Decimal - • Enable LTO: Set
lto = truein release profile for smaller binaries - • Structured Logging: Use
tracinginstead ofprintln! - • Error Context: Always add context to errors with
.context()from anyhow - • Timeouts: Always set timeouts on HTTP requests and WebSocket connections
- • Graceful Shutdown: Use
tokio::signal::ctrl_c()for clean shutdown - • Testing: Use
mockitoorwiremockfor API testing
Conclusion
Rust is an excellent choice for building high-performance Polymarket trading bots. With proper async patterns, error handling, and performance optimizations, Rust bots can achieve good latency characteristics (typically ~500ms for API operations). Always use type-safe structs with serde, handle errors gracefully with context, and optimize for efficient network usage with connection pooling.
For production applications requiring trading infrastructure, consider using enterprise platforms like PolyTrack Pro to access pre-built API integrations and skip months of low-level development work.
Related Resources
Frequently Asked Questions
Essential crates include: tokio (async runtime), reqwest (HTTP client), tokio-tungstenite (WebSocket), serde/serde_json (JSON), rust_decimal (decimal math), anyhow/thiserror (error handling), and chrono (time). Enable TCP_NODELAY for low-latency HTTP requests.
Related Articles
Stop Guessing. Start Following Smart Money.
Get instant alerts when whales make $10K+ trades. Track P&L, win rates, and copy winning strategies.