Skip to content

Repository files navigation

dxlink

dxlink

dxlink is a Rust client library for the DXLink WebSocket protocol used by tastytrade for real-time market data. This library provides a clean and type-safe API for connecting to DXLink servers, subscribing to market events, and processing real-time market data.

What is supported

  • Session lifecycle over the DXLink WebSocket protocol: SETUP, token authentication, feed channels, FEED_SETUP, subscribe and unsubscribe.
  • Subscriptions tracked per channel, with fromTime and source, and committed only after the outbound send succeeds. Two channels can hold the same event and symbol independently, and resetting or closing one leaves the others alone. [DXLinkClient::subscriptions] reports what the client believes is live.
  • Automatic keepalives while the connection is open.
  • Market events decoded from the COMPACT wire format: Quote, Trade, Greeks, Candle, Summary, TimeAndSale, Profile, Underlying, TheoPrice, TradeETH and Series, each with the full field set the dxFeed schema defines for it.
  • Both delivery styles: a per-symbol callback and a single event stream.
  • Historical data via from_time on a Candle subscription, decoded into OHLC bars.
  • Typed errors ([DXLinkError]) with [DXLinkError::is_terminal] to tell a lost connection from one bad message.
  • Strict decoding: [try_parse_compact_data] reports a short row, a wrong column type or an unknown event type instead of returning fewer events and letting a consumer read that as a quiet market.
  • The layout the server negotiates is the one that gets decoded. A feed may serve fewer fields than were asked for — the dxFeed demo drops VWAP from Candle — so columns are read by name rather than by position. A field the server does not send arrives as NaN, 0 or an empty string rather than as its neighbour's value, and a reordered list is followed rather than refused.
  • A lost connection is observable: the event stream closes, and [DXLinkClient::disconnect_reason] says why.
  • Opt-in reconnection. Off by default. Install a [ReconnectPolicy] with [DXLinkClient::with_reconnect] before connecting and a terminal socket failure is followed by exponential backoff, a fresh handshake, and a replay of every channel, feed configuration and subscription. A rejected token is not retried. [DXLinkClient::connection_states] reports what is happening, dropping the oldest states rather than the newest if a consumer falls behind, so a slow reader can miss the middle of a reconnect but not its outcome.

What is not supported yet

  • [EventType] declares more variants than the library can decode. Only Quote, Trade, Greeks, Candle, Summary, TimeAndSale, Profile, Underlying, TheoPrice, TradeETH and Series produce a [MarketEvent], and configuring or subscribing to any other type is refused rather than accepted into a stream that can never produce.

Minimum supported Rust version

Rust 1.88. The crate is edition 2024 and uses let-chains, which 1.87 rejects. Raising this floor is a compatibility break and is called out in the release notes; CI builds and tests the full workspace on it so a dependency upgrade cannot raise it silently.

ref: https://raw.githubusercontent.com/dxFeed/dxLink/refs/heads/main/dxlink-specification/asyncapi.yml

Example

Here's a basic example of using the library to connect to a DXLink server and subscribe to market data:

use std::error::Error;
use dxlink::{DXLinkClient, EventType, FeedSubscription, MarketEvent};
use tokio::time::sleep;
use std::time::Duration;

#[tokio::main]
async fn main() -> Result<(), Box<dyn Error>> {

    use tracing::info;

    // Create a new DXLink client with the API token
    // (typically obtained from the tastytrade API)
    let token = "your_api_token_here";
    let url = "wss://tasty-demo-dxlink-md-ws.dxfeed.com/delayed";
    let mut client = DXLinkClient::new(url, token);

    // Connect to the DXLink server. This returns the event stream; there is
    // exactly one, and asking for it again is an error.
    let mut event_stream = client.connect().await?;

    // Create a feed channel with AUTO contract type
    let channel_id = client.create_feed_channel("AUTO").await?;

    // Configure the channel for Quote and Trade events
    client.setup_feed(channel_id, &[EventType::Quote, EventType::Trade]).await?;

    // Register a callback for specific symbol
    client.on_event("SPY", |event| {
        info!("Event received for SPY: {:?}", event);
    });

    // Process events in a separate task
    tokio::spawn(async move {
        while let Some(event) = event_stream.recv().await {
            match &event {
                MarketEvent::Quote(quote) => {
                    info!(
                        "Quote: {} - Bid: {} x {}, Ask: {} x {}",
                        quote.event_symbol,
                        quote.bid_price,
                        quote.bid_size,
                        quote.ask_price,
                        quote.ask_size
                    );
                },
                MarketEvent::Trade(trade) => {
                    info!(
                        "Trade: {} - Price: {}, Size: {}, Volume: {}",
                        trade.event_symbol,
                        trade.price,
                        trade.size,
                        trade.day_volume
                    );
                },
                _ => info!("Other event type: {:?}", event),
            }
        }
    });

    // Subscribe to some symbols
    let subscriptions = vec![
        FeedSubscription {
            event_type: "Quote".to_string(),
            symbol: "SPY".to_string(),
            from_time: None,
            source: None,
        },
        FeedSubscription {
            event_type: "Trade".to_string(),
            symbol: "SPY".to_string(),
            from_time: None,
            source: None,
        },
    ];

    client.subscribe(channel_id, subscriptions).await?;

    // Keep the connection active for some time
    sleep(Duration::from_secs(60)).await;

    // Cleanup
    client.disconnect().await?;

    Ok(())
}

Working with historical data

DXLink supports subscribing to historical data through Candle events, by specifying the period, type, and a timestamp to fetch from.

The bars come back as [MarketEvent::Candle]:

use dxlink::FeedSubscription;
use std::time::{SystemTime, UNIX_EPOCH};

// Get current timestamp in milliseconds
let now = SystemTime::now()
    .duration_since(UNIX_EPOCH)
    .unwrap()
    .as_millis() as i64;

// Timestamp for 24 hours ago
let one_day_ago = now - (24 * 60 * 60 * 1000);

// Subscribe to 5-minute candles for SPY for the last 24 hours
let candle_subscription = FeedSubscription {
    event_type: "Candle".to_string(),
    symbol: "SPY{=5m}".to_string(),  // 5-minute candles
    from_time: Some(one_day_ago),
    source: None,
};

Error Handling

The library uses a custom error type DXLinkError that encompasses various error cases that can occur when interacting with the DXLink API:

use tracing::{error, info};
use dxlink::{DXLinkClient, DXLinkError};

async fn example_error_handling() {
    let mut client = DXLinkClient::new("wss://example.com", "token");
    match client.connect().await {
        Ok(_) => info!("Connected successfully!"),
        Err(DXLinkError::Authentication(e)) => error!("Authentication failed: {}", e),
        Err(DXLinkError::Connection(e)) => error!("Connection error: {}", e),
        Err(e) => error!("Other error: {}", e),
    }
}

Available Event Types

[EventType] mirrors the event types DXLink itself defines, but only these are decoded into a [MarketEvent] and delivered:

Event type Decoded fields
Quote bidPrice, askPrice, bidSize, askSize
Trade price, size, dayVolume
Greeks delta, gamma, theta, vega, rho, volatility
Candle the full 18-column layout: OHLCV plus VWAP, bid/ask volume, implied volatility, open interest and the snapshot flags
Summary the full 14-column layout: day and previous-day prices, their price types, volume and open interest
TimeAndSale the full 22-column layout: price, size and the surrounding quote plus exchange, sale conditions, aggressor side and the print flags
Profile the full 20-column layout: description, trading status and halt window, price limits, 52-week range and the fundamentals
Underlying the full 13-column layout: implied volatility with its term structure, call and put volume and their ratio
TheoPrice the full 13-column layout: the theoretical price with the underlying price, delta, gamma, dividend and interest it came from
TradeETH the full 15-column layout: the extended-hours print, its session volume and turnover, tick direction and the extended-hours flag
Series the full 15-column layout: per-expiration implied volatility, call and put volume, forward price and the dividend and interest inputs

Configuring or subscribing to any other variant (Order, SpreadOrder, …) is refused, rather than accepted into a stream that can never produce.

License

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

Setup Instructions

  1. Clone the repository:
git clone https://github.com/joaquinbejar/DXlink
cd DXlink
  1. Build the project:
make build
  1. Run tests:
make test
  1. Format the code:
make fmt
  1. Run linting:
make lint
  1. Clean the project:
make clean
  1. Run the project:
make run
  1. Fix issues:
make fix
  1. Run pre-push checks:
make pre-push
  1. Generate documentation:
make doc
  1. Publish the package:
make publish
  1. Generate coverage report:
make coverage

Testing

To run unit tests:

make test

To run tests with coverage:

make coverage

Contribution and Contact

We welcome contributions to this project! If you would like to contribute, please follow these steps:

  1. Fork the repository.
  2. Create a new branch for your feature or bug fix.
  3. Make your changes and ensure that the project still builds and all tests pass.
  4. Commit your changes and push your branch to your forked repository.
  5. Submit a pull request to the main repository.

If you have any questions, issues, or would like to provide feedback, please feel free to contact the project maintainer:

Joaquín Béjar García

We appreciate your interest and look forward to your contributions!

License: MIT

About

dxlink is a Rust client library for the DXLink WebSocket protocol used by tastytrade for real-time market data. This library provides a clean and type-safe API for connecting to DXLink servers, subscribing to market events, and processing real-time market data.

Topics

Resources

Stars

13 stars

Watchers

1 watching

Forks

Releases

Packages

Used by

Contributors

Languages