# Rust Rithmic R | Protocol API client
[](https://crates.io/crates/rithmic-rs)
[](https://docs.rs/rithmic-rs)
[](https://github.com/pbeets/rithmic-rs/actions)
[](LICENSE-MIT)
[Official Rithmic API](https://www.rithmic.com/apis)
## Quick Start
Add to your `Cargo.toml`:
```toml
[dependencies]
rithmic-rs = "2.0.0"
```
Set your environment variables:
```sh
RITHMIC_APP_NAME=your_app_name
RITHMIC_APP_VERSION=1
RITHMIC_DEMO_USER=your_username
RITHMIC_DEMO_PW=your_password
RITHMIC_DEMO_URL=<provided_by_rithmic>
RITHMIC_DEMO_ALT_URL=<provided_by_rithmic>
# Required for order and PnL requests
RITHMIC_DEMO_ACCOUNT_ID=your_account_id
RITHMIC_DEMO_FCM_ID=your_fcm_id
RITHMIC_DEMO_IB_ID=your_ib_id
# See examples/.env.blank for Live
```
`RithmicConfig` contains connection and login details. `RithmicAccount` is separate and
identifies which trading account to use for order and PnL requests. Login is user-scoped;
account fields are sent with account-scoped requests, not during the initial sign-in.
Stream live market data:
```rust
use rithmic_rs::{RithmicConfig, RithmicEnv, ConnectStrategy, RithmicTickerPlant};
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let config = RithmicConfig::from_env(RithmicEnv::Demo)?; // for live RithmicEnv::Live
let plant = RithmicTickerPlant::connect(&config, ConnectStrategy::Retry).await?;
let mut handle = plant.get_handle();
handle.login().await?;
handle.subscribe("ESM6", "CME").await?; // Update to current front-month ES contract
while let Ok(update) = handle.subscription_receiver.recv().await {
println!("{:?}", update.message);
}
Ok(())
}
```
See [`examples/`](examples/) for more usage patterns including [reconnection handling](examples/reconnect.rs) and historical data loading.
## Architecture
This library uses the actor pattern where each Rithmic service runs independently in its own thread. All communication happens through tokio channels.
- **`RithmicTickerPlant`** - Real-time market data (trades, quotes, order book)
- **`RithmicOrderPlant`** - Order entry and management
- **`RithmicHistoryPlant`** - Historical tick and bar data
- **`RithmicPnlPlant`** - Position and P&L tracking
### Ticker Plant
```rust
// Subscribe to real-time quotes
handle.subscribe("ESM6", "CME").await?;
// Unsubscribe when done
handle.unsubscribe("ESM6", "CME").await?;
// Additional market data subscriptions
handle.subscribe_instrument_status("ESM6", "CME").await?;
handle.subscribe_open_interest("ESM6", "CME").await?;
handle.subscribe_session_prices("ESM6", "CME").await?;
handle.subscribe_order_price_limits("ESM6", "CME").await?;
// Symbol discovery
let symbols = handle.search_symbols("ES", Some("CME"), None, None, None).await?;
let front_month = handle.get_front_month_contract("ES", "CME", false).await?;
```
### Order Plant
```rust
use rithmic_rs::{
ConnectStrategy, NewOrderPriceType, NewOrderTransactionType, RithmicAccount,
RithmicConfig, RithmicEnv, RithmicOrder, RithmicOrderPlant,
};
let config = RithmicConfig::from_env(RithmicEnv::Demo)?;
let account = RithmicAccount::from_env(RithmicEnv::Demo)?;
let plant = RithmicOrderPlant::connect(&config, ConnectStrategy::Retry).await?;
let mut handle = plant.get_handle(&account);
handle.login().await?;
// Place orders using the RithmicOrder API
let order = RithmicOrder {
symbol: "ESM6".to_string(),
exchange: "CME".to_string(),
quantity: 1,
price: 5000.0,
transaction_type: NewOrderTransactionType::Buy,
price_type: NewOrderPriceType::Limit,
user_tag: "my-order".to_string(),
..Default::default()
};
handle.place_order(order).await?;
// Bracket orders, OCO orders, advanced bracket orders
handle.place_bracket_order(...).await?;
handle.place_advanced_bracket_order(advanced_order).await?;
// Manage positions
handle.cancel_order(order_id).await?;
handle.exit_position("ESM6", "CME").await?;
```
For multi-account workflows, create one [`RithmicAccount`] per account and call
`get_handle(&account)` for each handle you need.
### History Plant
```rust
// Load historical data (bar_type, period, start_time, end_time as i32 unix seconds)
let bars = handle.load_time_bars("ESM6", "CME", BarType::MinuteBar, 5, start, end).await?;
let ticks = handle.load_ticks("ESM6", "CME", start, end).await?;
// Load N-tick bars (e.g., 5-tick bars)
let tick_bars = handle.load_tick_bars("ESM6", "CME", 5, start, end).await?;
```
### PnL Plant
```rust
use rithmic_rs::{
ConnectStrategy, RithmicAccount, RithmicConfig, RithmicEnv, RithmicPnlPlant,
};
let config = RithmicConfig::from_env(RithmicEnv::Demo)?;
let account = RithmicAccount::from_env(RithmicEnv::Demo)?;
let plant = RithmicPnlPlant::connect(&config, ConnectStrategy::Retry).await?;
let mut handle = plant.get_handle(&account);
handle.login().await?;
// Monitor P&L
handle.subscribe_pnl_updates().await?;
let snapshot = handle.pnl_position_snapshots().await?;
```
## Error Handling
All plant handle methods return `Result<_, RithmicError>` with typed variants you can match on for recovery decisions:
```rust
use rithmic_rs::RithmicError;
match handle.subscribe("ESM6", "CME").await {
Ok(resp) => { /* success */ }
Err(RithmicError::ConnectionClosed | RithmicError::SendFailed) => {
handle.abort();
// reconnect — see examples/reconnect.rs
}
Err(RithmicError::InvalidArgument(msg)) => eprintln!("Bad argument: {}", msg),
Err(RithmicError::RequestRejected(err)) => {
eprintln!(
"Server rejected: code={} msg={}",
err.code.as_deref().unwrap_or("?"),
err.message.as_deref().unwrap_or(""),
);
}
Err(RithmicError::ProtocolError(msg)) => eprintln!("Protocol error: {}", msg),
Err(e) => eprintln!("{}", e),
}
```
When inspecting a `RithmicResponse` directly (for example, entries from a
subscription broadcast), match on `response.error` — it is `Option<RithmicError>`.
Use [`RithmicError::is_connection_issue`] to distinguish transport failures from
protocol rejections. The raw rp_code payload is also accessible:
- `response.rp_code() -> Option<&[String]>` — full raw payload as received.
- `response.rp_code_num() -> Option<&str>` — numeric code (first element).
- `response.rp_code_text() -> Option<&str>` — human message (second element).
`RithmicError` implements `std::error::Error`, so `?` works in functions returning `Box<dyn Error>`.
## Connection Strategies
Three strategies for initial connection:
- **`Simple`**: Single attempt, fast-fail
- **`Retry`**: Exponential backoff, capped at 60 seconds (recommended default)
- **`AlternateWithRetry`**: Alternates between primary and alt URLs
### Reconnection
If you need to handle disconnections and automatically reconnect, you must implement your own reconnection loop. See [`examples/reconnect.rs`](examples/reconnect.rs) for a complete example that tracks subscriptions and re-subscribes after reconnect.
## Upgrading
See [CHANGELOG.md](CHANGELOG.md) for version history.
## Contribution
Contributions encouraged and welcomed!
Unless you explicitly state otherwise, any contribution intentionally submitted
for inclusion in the work by you, as defined in the Apache-2.0 license, shall be
dual licensed as below, without any additional terms or conditions.
## License
Licensed under either of [Apache License, Version 2.0](LICENSE-APACHE) or
[MIT license](LICENSE-MIT) at your option.