oanda-rs 0.1.0

Async Rust SDK for the OANDA v20 REST and streaming API
Documentation

oanda-rs

CI codecov crates.io docs.rs License: BSD-3-Clause MSRV

A complete, asynchronous Rust SDK for the OANDA v20 REST and streaming API, built for multi-threaded tokio environments.

Highlights

  • Complete — all 35 v20 operations (accounts, instruments, orders, trades, positions, pricing, transactions) plus both streaming endpoints. See the coverage table.
  • One shared clientClient is cheap to clone and Send + Sync; all clones share a connection pool and a built-in rate limiter that keeps you under OANDA's per-IP limits (100 of 120 REST req/s, 2 connections/s). No accidental 429s.
  • Self-managing streams — pricing and transaction streams detect stale connections via heartbeats, reconnect with capped exponential backoff (outage-safe: at most one attempt per 5 minutes during e.g. weekend maintenance), and back-fill missed transactions via sinceid with deduplication. Consuming them is just a while let loop.
  • Typed, faithful models — every request/response type is Debug + Clone + Serialize + Deserialize. Decimals are rust_decimal::Decimal newtypes (never floats). The 36-variant Transaction and 8-variant Order unions are internally tagged enums with lossless Unknown fallbacks, so schema drift never breaks deserialization.
  • Lightweight builders — optional parameters via fluent per-endpoint builders; typed order requests (MarketOrderRequest::new("EUR_USD", 100).stop_loss_on_fill(...)).
  • Typed instrumentsInstrumentName enumerates all known OANDA symbols (with an Other escape hatch), while every API still accepts plain "EUR_USD" strings.

Installation

[dependencies]
oanda-rs = "0.1"
tokio = { version = "1", features = ["macros", "rt-multi-thread"] }
futures-util = "0.3"

Quickstart

use futures_util::StreamExt;
use oanda_rs::prelude::*;

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let client = Client::new(Environment::Practice, std::env::var("OANDA_TOKEN")?);

    // REST: place a market order with a stop loss.
    let account = "101-004-1234567-001";
    let response = client
        .create_order(
            account,
            MarketOrderRequest::new(InstrumentName::EurUsd, 100)
                .stop_loss_on_fill(StopLossDetails::at_distance("0.0050".parse()?)),
        )
        .await?;
    println!("filled: {:?}", response.order_fill_transaction.is_some());

    // Streaming: live prices with automatic reconnection.
    let mut prices = client.pricing_stream(account, ["EUR_USD"]).send().await?;
    while let Some(item) = prices.next().await {
        if let PriceStreamItem::Price(price) = item? {
            println!("{:?} / {:?}", price.closeout_bid, price.closeout_ask);
        }
    }
    Ok(())
}

More runnable examples in examples/: list_accounts, candles, market_order, pricing_stream, transaction_stream.

Feature flags

Feature Default Description
tracing off DEBUG-level instrumentation of requests and stream reconnection.

Documentation

Testing

~90 tests cover every endpoint (wiremock), every Transaction/Order variant (byte-exact serde round-trips), NDJSON framing edge cases, and the reconnect state machine under paused tokio time. An opt-in live suite validates against fxPractice:

cargo test                            # full mock-based suite
cargo test --test live -- --ignored   # read-only live tests (needs .env)

Disclaimer

This is an unofficial SDK, not affiliated with or endorsed by OANDA. Trading foreign exchange carries a high level of risk. Develop against the fxTrade Practice environment until you are confident in your integration.

License

BSD 3-Clause — see LICENSE.