kraken-ws-sdk 0.2.0

A high-performance, production-grade SDK for Kraken's WebSocket API with backpressure control, latency tracking, and chaos engineering support
docs.rs failed to build kraken-ws-sdk-0.2.0
Please check the build logs for more information.
See Builds for ideas on how to fix a failed build, or Metadata for how to configure docs.rs builds.
If you believe this is docs.rs' fault, open an issue.
Visit the last successful build: kraken-ws-sdk-0.1.0

Kraken WebSocket SDK

Crates.io Rust License: MIT docs.rs

A production-grade Rust SDK for Kraken's WebSocket API with a frozen, minimal API surface and deterministic connection state machine.

API Stability

This SDK follows a frozen API philosophy for production reliability:

Module Stability Description
prelude Stable Core API - won't break between minor versions
extended Stable Advanced features - stable but may grow
internal Unstable Implementation details - may change
// βœ… Use this for production code
use kraken_ws_sdk::prelude::*;

// βœ… For advanced features
use kraken_ws_sdk::extended::*;

// ❌ Don't depend on internal modules
// use kraken_ws_sdk::internal::*;

Features

  • πŸ”’ Frozen API: Minimal, stable surface - trading firms hate churn
  • 🎯 Deterministic State Machine: Explicit connection states with single-cause transitions
  • πŸš€ High Performance: Async processing with minimal overhead
  • πŸ“Š Real-time Data: Tickers, trades, order books, OHLC
  • πŸ”„ Auto Recovery: Exponential backoff with configurable retry limits
  • πŸ“ˆ Order Book Management: Snapshot + delta stitching with checksum validation

Quick Start

Add this to your Cargo.toml:

[dependencies]
kraken-ws-sdk = "0.1.0"
tokio = { version = "1.0", features = ["full"] }

Stream API (Recommended)

The simplest way to consume events - a single unified stream:

use kraken_ws_sdk::{KrakenWsClient, ClientConfig, Channel, SdkEvent};

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let mut client = KrakenWsClient::new(ClientConfig::default());
    
    // Get unified event stream
    let mut events = client.events();
    
    // Subscribe and connect
    client.subscribe(vec![
        Channel::new("ticker").with_symbol("BTC/USD"),
        Channel::new("trade").with_symbol("BTC/USD"),
    ]).await?;
    client.connect().await?;
    
    // Process all events in one place
    while let Some(event) = events.recv().await {
        match event {
            SdkEvent::Ticker(t) => println!("πŸ“Š {}: ${}", t.symbol, t.last_price),
            SdkEvent::Trade(t) => println!("πŸ’° {}: {} @ ${}", t.symbol, t.volume, t.price),
            SdkEvent::OrderBook(b) => println!("πŸ“– {}: {} bids", b.symbol, b.bids.len()),
            SdkEvent::State(s) => println!("πŸ”— Connection: {:?}", s),
            SdkEvent::Error(e) => eprintln!("❌ Error: {}", e),
            _ => {}
        }
    }
    Ok(())
}

Callback API (Traditional)

For more control, register callbacks per data type:

use kraken_ws_sdk::{
    KrakenWsClient, ClientConfig, Channel, DataType, EventCallback,
    TickerData, TradeData, ConnectionState, SdkError
};
use std::sync::Arc;

struct MyCallback;

impl EventCallback for MyCallback {
    fn on_ticker(&self, data: TickerData) {
        println!("Ticker: {} - Last: {}", data.symbol, data.last_price);
    }
    
    fn on_trade(&self, data: TradeData) {
        println!("Trade: {} - {} @ {}", data.symbol, data.volume, data.price);
    }
    
    fn on_orderbook(&self, data: OrderBookUpdate) {
        println!("Order Book: {} - {} bids, {} asks", 
            data.symbol, data.bids.len(), data.asks.len());
    }
    
    fn on_ohlc(&self, data: OHLCData) {
        println!("OHLC: {} - Close: {}", data.symbol, data.close);
    }
    
    fn on_error(&self, error: SdkError) {
        eprintln!("Error: {}", error);
    }
    
    fn on_connection_state_change(&self, state: ConnectionState) {
        println!("Connection: {:?}", state);
    }
}

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let mut client = KrakenWsClient::new(ClientConfig::default());
    
    // Register callbacks
    let callback: Arc<dyn EventCallback> = Arc::new(MyCallback);
    client.register_callback(DataType::Ticker, callback.clone());
    client.register_callback(DataType::Trade, callback);
    
    // Connect and subscribe
    client.subscribe(vec![
        Channel::new("ticker").with_symbol("BTC/USD"),
        Channel::new("trade").with_symbol("BTC/USD"),
    ]).await?;
    client.connect().await?;
    
    // Keep running
    tokio::time::sleep(std::time::Duration::from_secs(30)).await;
    client.cleanup().await?;
    Ok(())
}

Configuration

Basic Configuration

use kraken_ws_sdk::{ClientConfig, ReconnectConfig};
use std::time::Duration;

let config = ClientConfig {
    endpoint: "wss://ws.kraken.com".to_string(),
    timeout: Duration::from_secs(30),
    buffer_size: 1024,
    reconnect_config: ReconnectConfig {
        max_attempts: 10,
        initial_delay: Duration::from_millis(100),
        max_delay: Duration::from_secs(30),
        backoff_multiplier: 2.0,
    },
    ..Default::default()
};

Authentication (for private channels)

let config = ClientConfig {
    api_key: Some("your_api_key".to_string()),
    api_secret: Some("your_api_secret".to_string()),
    ..Default::default()
};

Supported Channels

Channel Description Public Private
ticker Real-time ticker data βœ… ❌
trade Real-time trade data βœ… ❌
book Order book updates βœ… ❌
ohlc OHLC/candlestick data βœ… ❌
spread Spread data βœ… ❌
ownTrades User's trades ❌ βœ…
openOrders User's open orders ❌ βœ…

Data Types

TickerData

pub struct TickerData {
    pub symbol: String,
    pub bid: Decimal,
    pub ask: Decimal,
    pub last_price: Decimal,
    pub volume: Decimal,
    pub timestamp: DateTime<Utc>,
}

TradeData

pub struct TradeData {
    pub symbol: String,
    pub price: Decimal,
    pub volume: Decimal,
    pub side: TradeSide,
    pub timestamp: DateTime<Utc>,
    pub trade_id: String,
}

OrderBookUpdate

pub struct OrderBookUpdate {
    pub symbol: String,
    pub bids: Vec<PriceLevel>,
    pub asks: Vec<PriceLevel>,
    pub timestamp: DateTime<Utc>,
    pub checksum: Option<u32>,
}

Error Handling

The SDK provides comprehensive error handling through the SdkError enum:

pub enum SdkError {
    Connection(ConnectionError),
    Parse(ParseError),
    Subscription(SubscriptionError),
    Configuration(String),
    Network(String),
    Authentication(String),
}

Error Recovery

impl EventCallback for MyCallback {
    fn on_error(&self, error: SdkError) {
        match error {
            SdkError::Connection(conn_err) => {
                // Connection will auto-reconnect
                println!("Connection error: {}", conn_err);
            }
            SdkError::Parse(parse_err) => {
                // Parser will continue with next message
                println!("Parse error: {}", parse_err);
            }
            SdkError::Subscription(sub_err) => {
                // May need to resubscribe
                println!("Subscription error: {}", sub_err);
            }
            _ => {
                println!("Other error: {}", error);
            }
        }
    }
}

Order Book Management

The SDK includes built-in order book state management:

// Get current order book
if let Some(order_book) = client.get_order_book("BTC/USD") {
    println!("Spread: {:?}", order_book.get_spread());
    println!("Mid price: {:?}", order_book.get_mid_price());
    
    let (bid_volume, ask_volume) = order_book.get_total_volume();
    println!("Total volume - Bids: {}, Asks: {}", bid_volume, ask_volume);
}

// Get best bid/ask
if let Some((best_bid, best_ask)) = client.get_best_bid_ask("BTC/USD") {
    println!("Best bid: {:?}, Best ask: {:?}", best_bid, best_ask);
}

Advanced Features

Multiple Callbacks

// Register multiple callbacks for the same data type
let callback1 = Arc::new(LoggingCallback);
let callback2 = Arc::new(MetricsCallback);

client.register_callback(DataType::Ticker, callback1);
client.register_callback(DataType::Ticker, callback2);

Connection State Monitoring

client.register_connection_listener(Arc::new(ConnectionMonitor));

// Check connection state
match client.connection_state() {
    ConnectionState::Connected => println!("Connected"),
    ConnectionState::Reconnecting => println!("Reconnecting..."),
    ConnectionState::Failed => println!("Connection failed"),
    _ => {}
}

Subscription Management

// Check active subscriptions
let subscriptions = client.get_active_subscriptions();
println!("Active subscriptions: {:?}", subscriptions);

// Check if subscribed to specific channel
let channel = Channel::new("ticker").with_symbol("BTC/USD");
if client.is_subscribed(&channel) {
    println!("Subscribed to BTC/USD ticker");
}

Examples

See the examples/ directory for complete examples:

Running Examples

Command Line Examples:

cargo run --example basic_usage
cargo run --example advanced_usage

Web Demo Dashboard:

# Option 1: Use the launcher script
./scripts/run_web_demo.sh

# Option 2: Run directly
cd examples/web_demo && cargo run

Then open your browser to: http://localhost:3032

🌐 Web Demo Features

The web demo connects to live Kraken WebSocket API - no mocks, no simulations, real market data:

  • πŸ“Š Live Market Data - Real-time prices from Kraken's production WebSocket feed
  • πŸ”Œ Direct WebSocket - Connects to wss://ws.kraken.com for live ticker/trade streams
  • πŸ“ˆ SDK Observability - Health monitoring, latency tracking, sequence gap detection
  • πŸŽ›οΈ Dynamic Subscriptions - Add/remove pairs on the fly (max 5)
  • πŸ”„ Auto Reconnection - Demonstrates SDK's connection resilience
  • πŸ“± Responsive Design - Works on desktop, tablet, and mobile

Default pairs: BTC/USD, ETH/USD, SOL/USD

Connection State Machine

The SDK uses a deterministic state machine for connection management. Each state has explicit transitions with single causes and actions.

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”                    β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”                    β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ DISCONNECTED│───── connect() ───▢│  CONNECTING │───── success ─────▢│AUTHENTICATING β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜                    β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜                    β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
       β–²                                  β”‚                                   β”‚
       β”‚                               failure                          success/skip
       β”‚                                  β”‚                                   β”‚
       β”‚                                  β–Ό                                   β–Ό
       β”‚                           β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”                        β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
       β”‚                           β”‚ DEGRADED │◀─── subscription ──────│ SUBSCRIBING β”‚
       β”‚                           β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜      failed            β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
       β”‚                                  β”‚                                   β”‚
       β”‚                               retry                              success
       β”‚                                  β”‚                                   β”‚
       β”‚                                  β–Ό                                   β–Ό
       β”‚                           β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”    gap_detected       β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
       └────── close() ────────────│  CLOSED  │◀───────────────────────│ SUBSCRIBED β”‚
                                   β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜                        β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                                        β–²                                    β”‚
                                        β”‚                              gap_detected
                                   max_retries                               β”‚
                                        β”‚                                    β–Ό
                                        β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
                                                                       β”‚ RESYNCING β”‚
                                                                       β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

State Descriptions

State Description Exit Conditions
DISCONNECTED Initial state connect() β†’ CONNECTING
CONNECTING Establishing WebSocket success β†’ AUTHENTICATING, failure β†’ DEGRADED
AUTHENTICATING Sending API credentials success β†’ SUBSCRIBING, failure β†’ DEGRADED
SUBSCRIBING Sending subscription requests all confirmed β†’ SUBSCRIBED, failure β†’ DEGRADED
SUBSCRIBED Receiving data normally gap β†’ RESYNCING, disconnect β†’ DEGRADED, close() β†’ CLOSED
RESYNCING Recovering from sequence gap complete β†’ SUBSCRIBED, failure β†’ DEGRADED
DEGRADED Attempting recovery retry β†’ CONNECTING, max_retries β†’ CLOSED
CLOSED Terminal state connect() starts new connection

State Events

Every state transition emits an Event::StateChange(ConnectionState):

use kraken_ws_sdk::prelude::*;

let mut events = client.events();
while let Some(event) = events.recv().await {
    match event {
        Event::StateChange(state) => {
            match state {
                ConnectionState::Subscribed => println!("βœ… Ready to receive data"),
                ConnectionState::Degraded { reason, retry_count, .. } => {
                    println!("⚠️ Degraded: {:?}, retry #{}", reason, retry_count);
                }
                ConnectionState::Closed { reason } => {
                    println!("❌ Closed: {:?}", reason);
                    break;
                }
                _ => {}
            }
        }
        _ => {}
    }
}

Correctness Guarantees

This section defines the SDK's behavioral contract. These are guarantees, not just features.

Message Ordering

Scope Guarantee Notes
Per symbol, per channel Strictly ordered Ticker updates for BTC/USD arrive in exchange order
Per symbol, across channels No guarantee Ticker and trade for same symbol may interleave
Across symbols No guarantee BTC and ETH updates are independent streams

Kraken's guarantee: Messages within a channel/pair are sequenced. The SDK preserves this ordering.

Heartbeat & Liveness

Mechanism Interval SDK Behavior
Kraken ping 30s SDK responds with pong automatically
SDK heartbeat Configurable (default 30s) Sends ping, expects pong within timeout
No response After timeout Connection marked stale, reconnect triggered
Kraken systemStatus On connect Logged, on_connection_state_change fired

Kraken endpoints:

  • Public: wss://ws.kraken.com - No auth required, ticker/trade/book/ohlc
  • Private: wss://ws-auth.kraken.com - Requires API key, ownTrades/openOrders

Reconnection Behavior

Event SDK Behavior User Action Required
Network disconnect Auto-reconnect with exponential backoff (100ms β†’ 30s) None
Server close (1000) Reconnect after initial_delay None
Auth failure Stop reconnecting, emit error Re-authenticate
Max attempts reached Emit ConnectionState::Failed Manual connect()

On successful reconnect:

  1. All previous subscriptions are automatically restored
  2. on_connection_state_change(Connected) fires
  3. Order book state is invalidated - snapshot required before deltas

Sequence Gap Handling

Expected: seq 100 β†’ Received: seq 105
         ↓
    Gap detected (size: 5)
         ↓
    on_gap_detected(expected=100, received=105)
         ↓
    Resync triggered (request snapshot)
         ↓
    on_resync(reason=GapDetected)

Gap policies:

  • GapPolicy::Resync (default) - Request fresh snapshot on any gap
  • GapPolicy::Ignore - Continue processing, accept data loss
  • GapPolicy::Buffer - Buffer messages, attempt reorder within window

Order Book Stitching Rules

The SDK maintains order book state with these invariants:

  1. Snapshot first: Always wait for snapshot before applying deltas
  2. Checksum validation: Verify CRC32 checksum after each update (Kraken provides this)
  3. Sequence ordering: Deltas must be applied in sequence order
  4. Stale detection: Discard deltas older than current snapshot sequence
State Machine:
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”    snapshot    β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”    delta     β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ Disconnected│───────────────▢│ Snapshot Rcvd│─────────────▢│ Applying    β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜                β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜              β”‚ Deltas      β”‚
       β–²                              β–²                      β””β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”˜
       β”‚                              β”‚                             β”‚
       β”‚         checksum fail        β”‚      checksum ok            β”‚
       β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

During resync: Consumer receives BookState::Resyncing. Do not use stale book data.

Timestamp Guarantees

Guarantee Level Notes
Exchange timestamps Monotonic per symbol Kraken guarantees this
Receive timestamps Best effort Network jitter possible
Latency calculation receive_time - exchange_time Requires NTP sync

Clock skew: If your system clock drifts >1s from exchange, latency metrics will be inaccurate.


Tuning Guide

Buffer Sizes

Use Case buffer_size max_queue_depth Notes
Single pair, low freq 64 100 Minimal memory
Single pair, high freq 256 500 BTC/USD during volatility
10 pairs, mixed freq 512 1000 Typical trading bot
50+ pairs, all tickers 2048 5000 Market maker / aggregator
Order book depth 1000 4096 2000 Deep book tracking

Backpressure Configuration

BackpressureConfig {
    max_messages_per_second: 1000,  // Rate limit
    max_queue_depth: 500,           // Buffer before dropping
    drop_policy: DropPolicy::Oldest,  // What to drop
    coalesce_window_ms: 10,         // Merge window for same-symbol updates
}

Drop Policies:

Policy Behavior Best For
Oldest Remove oldest queued message Real-time displays
Latest Reject incoming message Audit/logging
Coalesce Merge updates for same symbol High-frequency tickers

Dropped vs Coalesced:

  • Dropped: Message discarded entirely, data loss
  • Coalesced: Multiple updates merged into one, no data loss but reduced granularity

Recommended Defaults

Low-latency trading (single pair):

ClientConfig {
    buffer_size: 128,
    timeout: Duration::from_millis(5000),
    ..Default::default()
}
BackpressureConfig {
    max_messages_per_second: 0,  // No limit
    drop_policy: DropPolicy::Oldest,
    ..Default::default()
}

Multi-pair monitoring (10-50 pairs):

ClientConfig {
    buffer_size: 1024,
    timeout: Duration::from_secs(30),
    ..Default::default()
}
BackpressureConfig {
    max_messages_per_second: 5000,
    coalesce_window_ms: 50,
    drop_policy: DropPolicy::Coalesce,
    ..Default::default()
}

High-frequency aggregator (100+ pairs):

ClientConfig {
    buffer_size: 4096,
    timeout: Duration::from_secs(60),
    ..Default::default()
}
BackpressureConfig {
    max_messages_per_second: 10000,
    max_queue_depth: 10000,
    coalesce_window_ms: 100,
    drop_policy: DropPolicy::Coalesce,
    ..Default::default()
}

Feature Flags

[dependencies]
# Minimal - public market data only
kraken-ws-sdk = "0.1"

# With private channels (requires API key)
kraken-ws-sdk = { version = "0.1", features = ["private"] }

# Full orderbook state management
kraken-ws-sdk = { version = "0.1", features = ["orderbook-state"] }

# Everything
kraken-ws-sdk = { version = "0.1", features = ["full"] }

# WebAssembly target
kraken-ws-sdk = { version = "0.1", default-features = false, features = ["wasm"] }
Feature Description Dependencies Added
public (default) Ticker, trades, book, OHLC Core only
private ownTrades, openOrders Auth modules
orderbook-state Full book management CRC32, state machine
metrics Prometheus export prometheus crate
chaos Fault injection None
wasm Browser support wasm-bindgen

Performance

The SDK is designed for high-performance applications:

  • Asynchronous: All operations are non-blocking
  • Zero-copy: Minimal data copying where possible
  • Efficient parsing: Optimized JSON parsing
  • Memory management: Automatic cleanup and leak prevention
  • Concurrent processing: Multiple callbacks can process data simultaneously

Testing

Run tests with:

cargo test

The SDK includes both unit tests and property-based tests for comprehensive coverage.

Contributing

Contributions are welcome! Please feel free to submit a Pull Request.

License

This project is licensed under the MIT License - see the LICENSE file for details.

Security

Credential Handling

DO NOT:

  • Log API keys or secrets (SDK redacts these automatically)
  • Commit .env files (use .env.example as template)
  • Pass credentials in URLs or query strings
  • Store credentials in code or version control

DO:

  • Use environment variables for credentials
  • Use .env files locally (gitignored)
  • Use secret managers in production (AWS Secrets Manager, Vault, etc.)
// βœ… GOOD: Load from environment
let api_key = std::env::var("KRAKEN_API_KEY").ok();
let api_secret = std::env::var("KRAKEN_API_SECRET").ok();

// ❌ BAD: Hardcoded credentials
let api_key = Some("abc123".to_string()); // NEVER DO THIS

What the SDK Logs

Data Logged? Notes
API Key ❌ Never Redacted to ***
API Secret ❌ Never Never appears in logs
Auth tokens ❌ Never Generated internally, not logged
Symbols/pairs βœ… Yes e.g., "BTC/USD"
Prices/volumes βœ… Yes Market data is public
Connection state βœ… Yes "Connected", "Reconnecting"
Error messages βœ… Yes Without sensitive data

Network Security

  • All connections use TLS 1.2+ (wss://)
  • Certificate validation is enabled by default
  • No plaintext WebSocket (ws://) in production

Reporting Vulnerabilities

If you discover a security vulnerability, please:

  1. Do not open a public issue
  2. Email security concerns to the maintainers
  3. Allow 90 days for a fix before public disclosure

Versioning & Compatibility

This SDK follows Semantic Versioning with a strong compatibility promise:

Post-1.0 Guarantee:

  • No breaking changes in prelude module without major version bump
  • Config additions are non-breaking (new fields have defaults)
  • New enum variants are non-breaking (#[non_exhaustive])
  • MSRV bumps require major version

Pre-1.0 (current):

  • Minor versions may include breaking changes (documented with BREAKING:)
  • Patch versions are always safe to upgrade

See CHANGELOG.md for detailed upgrade notes.


Disclaimer

This SDK is not officially affiliated with Kraken. Use at your own risk in production environments.

Support

For questions and support:

  • Check the examples directory
  • Review the API documentation: cargo doc --open
  • Open an issue on GitHub
  • See CHANGELOG.md for version history

Built with ❀️ in Rust