Expand description
§rustyschwab
An unofficial Rust SDK for the Charles Schwab Trader and Market Data APIs, not affiliated with or endorsed by Charles Schwab & Co., Inc.: OAuth with encrypted or keychain token storage, a REST client with retries, a shared rate budget and a circuit breaker, the streamer protocol, a durable order journal, and a paper-trading venue.
Wire types live in rustyschwab-types and are
re-exported as rustyschwab::types. The repository
README covers the MCP server,
operations and the full documentation set.
[dependencies]
rustyschwab = { version = "0.2", features = ["callback-server"] }
tokio = { version = "1", features = ["full"] }callback-server adds the local HTTPS listener that captures the OAuth
redirect. It serves the certificate in OAuthConfig::callback_cert_file
and callback_key_file (for example one from mkcert 127.0.0.1, which the
browser trusts), or a self-signed one the browser warns about.
AuthManager::authorize_with hands you the consent URL before it waits.
Without the feature, AuthManager::authorize returns the URL and you pass
the redirect to exchange_code yourself.
§Configure and authorize
SchwabConfig::from_env reads the SCHWAB_* variables listed in the
repository’s .env.example (SCHWAB_APP_KEY, SCHWAB_APP_SECRET,
SCHWAB_OAUTH_CALLBACK_URL, SCHWAB_TOKENS_FILE, SCHWAB_TOKEN_STORE, …)
and validates them.
use rustyschwab::prelude::*;
let client = SchwabClient::new(SchwabConfig::from_env()?)?;
let auth = client.auth_manager();
if !auth.has_valid_tokens() {
// Opens nothing by itself: print the URL, sign in, and the callback
// server (or `exchange_code`) stores the tokens.
let (url, _code) = auth.authorize().await?;
println!("Sign in at {url}");
}
client.init().await?; // background refresh§Read market data and accounts
use rustyschwab::prelude::*;
use rustyschwab::types::market_data::{FrequencyType, PeriodType, PriceHistoryParams};
let quotes = client.get_quotes(&["AAPL", "MSFT"]).await?;
let history = client
.get_price_history(&PriceHistoryParams {
period_type: Some(PeriodType::Day),
period: Some(10),
frequency_type: Some(FrequencyType::Minute),
frequency: Some(5),
..PriceHistoryParams::new("AAPL")
})
.await?;
let accounts = client.get_account_numbers().await?;§Place an order
Orders are built from exact decimals and validated before anything leaves
the process. A submission is sent once and never retried: an ambiguous
outcome is Error::SubmissionUnknown, and the account’s next submission
waits until the journal reconciles it against the broker.
use rustyschwab::SchwabClient;
use rustyschwab::types::Decimal;
use rustyschwab::types::trading::{OrderInstruction, OrderRequest, OrderRequestLeg};
// Buy 10 AAPL at 150.25, then take profit at 160 or stop out at 145.
let order = OrderRequest::limit(
OrderRequestLeg::equity(OrderInstruction::Buy, "AAPL", 10),
"150.25".parse()?,
)
.bracket(Decimal::from(160), Decimal::from(145))?;
let response = client.place_order(account_hash, &order).await?;
println!("order id: {:?}", response.order_id);§Stream quotes
The streamer identity (customer ID, channel, function ID) comes from the
user-preferences response. Consumers must handle StreamMessage::State:
when ready is false or the generation changes, derived state is stale.
use rustyschwab::{SchwabClient, StreamClient, StreamMessage};
use rustyschwab::config::StreamConfig;
use rustyschwab::types::streaming::{LevelOneEquity, StreamService};
let prefs = client.get_user_preferences().await?;
let info = prefs
.streamer_info
.as_deref()
.and_then(<[_]>::first)
.ok_or("no streamer info")?;
let stream = StreamClient::builder()
.config(StreamConfig::default())
.auth_manager(client.auth_manager())
.streamer_info(info)
.build()?;
stream.connect().await?;
stream
.subscribe(StreamService::LeveloneEquities, vec!["AAPL".into()])
.await?;
let mut receiver = stream.get_receiver().ok_or("receiver already taken")?;
while let Some(message) = receiver.recv().await {
match message {
StreamMessage::State(status) if !status.ready => {
// Session lost or messages dropped: re-baseline from REST.
}
StreamMessage::Data(data) => {
for item in data.data.iter().filter(|i| i.service() == Some(StreamService::LeveloneEquities)) {
for content in &item.content {
let quote = LevelOneEquity::from_content(content)?;
println!("{} bid {:?} ask {:?}", quote.key, quote.bid_price, quote.ask_price);
}
}
}
_ => {}
}
}§Features
| Feature | Default | Adds |
|---|---|---|
rustls | yes | rustls TLS for HTTP and WebSocket |
native-tls | no | the platform TLS stack instead |
callback-server | no | local HTTPS OAuth callback capture |
schemars | no | JSON Schema derives on the request types |
telemetry | yes | rustyschwab::telemetry: ready-made tracing-subscriber setups |
otel | no | telemetry::init_otel: OTLP/HTTP export of spans and, through the metrics facade, metrics, configured by the standard OTEL_* environment |
full | no | rustls, callback-server, schemars, telemetry |
§License
MIT or Apache-2.0, at your option.
Re-exports§
pub use auth::AuthManager;pub use auth::AuthResult;pub use auth::OAuthConfig;pub use auth::TokenSet;pub use circuit_breaker::CircuitBreaker;pub use circuit_breaker::CircuitState;pub use client::SchwabClient;pub use client::SchwabClientBuilder;pub use config::ChannelKind;pub use config::CircuitBreakerConfig;pub use config::ClientConfig;pub use config::SchwabConfig;pub use config::StreamConfig;pub use error::Error;pub use error::ErrorEnvelope;pub use error::ErrorKind;pub use error::Result;pub use streaming::MessageReceiver;pub use streaming::StreamClient;pub use streaming::Subscription;pub use rustyschwab_types as types;
Modules§
- audit_
chain - Tamper evidence for the
audittable. - audit_
delivery - Acknowledged, replay-safe delivery to an organization’s immutable audit sink.
- auth
- OAuth 2.0 authentication with PKCE and secure token storage.
- broker
- Common gateway execution boundary for live and isolated paper backends.
- circuit_
breaker - Circuit breaker pattern for cascading failure prevention.
- client
- Main Schwab API client for REST endpoints.
- config
- Configuration types for the Schwab SDK.
- coordination
- Coordination contract for local workers or a deployment-owned remote service.
- error
- Error types and handling for the Schwab SDK.
- execution
- Durable submission intent/outcome journal. Pending intents survive task cancellation and process failure, and block new submissions for that account.
- history
- A durable local cache of price history and recorded stream data.
- metrics
- Metric names, label conventions, and small helpers for SDK-side instrumentation.
- paper
- Isolated, durable long-equity paper execution and deterministic replay.
- performance
- Performance analytics over fills and mark-to-market equity curves.
- prelude
- Convenient re-exports for common Schwab SDK usage.
- readiness
- Readiness is local evidence, not a synthetic live broker probe.
- reconciliation
- Broker reconciliation is conservative: absence never proves rejection.
- recovery
- Restartable account recovery worker. Consumers can subscribe to readiness.
- remote
- Authenticated remote backends sharing one transactional authority. Use the broker gateway when workers must not possess broker credentials.
- retry
- Retry policy with exponential backoff and configurable behavior.
- risk
- Pluggable pre-trade risk with durable reservations for outstanding orders.
- security
- Security Module
- storage
- Private, transactional local state. SQLite files must be on a local filesystem.
- streaming
- telemetry
telemetry - Telemetry and observability initialization for the Schwab SDK.
- transport
- HTTP and WebSocket transport layer.
- utils
- Helpers shared by the digest, audit and reconciliation paths.
Structs§
- Request
Budget Usage - The in-process request budget of one credential at one instant.
Enums§
Constants§
- VERSION
- Library version string in the format
rustyschwab X.Y.Z.