11 releases (7 breaking)

new 0.7.0 Dec 15, 2024
0.6.0 Nov 21, 2024
0.5.0 Oct 20, 2024
0.4.1 Jul 31, 2024
0.0.1 Mar 19, 2024

#4 in #kraken

Download history 88/week @ 2024-08-31 42/week @ 2024-09-07 30/week @ 2024-09-14 23/week @ 2024-09-21 5/week @ 2024-09-28 1/week @ 2024-10-05 2/week @ 2024-10-12 166/week @ 2024-10-19 5/week @ 2024-10-26 3/week @ 2024-11-02 116/week @ 2024-11-16 24/week @ 2024-11-23 1/week @ 2024-11-30 73/week @ 2024-12-07

214 downloads per month

MIT license

670KB
16K SLoC

Kraken-Async-Rs

badge License: MIT codecov

A complete[^3] wrapper of the Kraken Pro trading API (v2 websockets), written in asynchronous Rust.

It's not expected that you'll be able to use Kraken-Async-Rs without consulting the Kraken REST API docs, and Kraken Websockets V2 docs.

There are many details and interdependencies[^1] to each request that are not documented or enforced in the library since they're outside this library's control and subject to change.

Example: Calling a Public Endpoint

Public endpoint calls are as easy as creating a client object and awaiting a request. Since no API secrets are required, a blank static set of credentials is provided using StaticSecretsProvider with empty &str values. See the full example for imports.

#[tokio::main]
async fn main() {
    // credentials aren't needed for public endpoints
    let secrets_provider: Arc<Mutex<Box<dyn SecretsProvider>>> = Box::new(Arc::new(Mutex::new(StaticSecretsProvider::new("", ""))));
    let nonce_provider: Arc<Mutex<Box<dyn NonceProvider>>> =
        Arc::new(Mutex::new(Box::new(IncreasingNonceProvider::new())));
    let mut client = CoreKrakenClient::new(secrets_provider, nonce_provider);

    let request = TradableAssetPairsRequest::builder()
        .pair(Pairs::new(vec!["BTCUSD".to_string()]))
        .build();

    let open_orders_response = client.get_tradable_asset_pairs(&request).await;

    // Note that Kraken will return assets in their own naming scheme, e.g. a request for
    // "BTCUSD" will return as "XXBTZUSD"
    // For a reasonable understanding of their mappings, see: https://gist.github.com/brendano257/975a395d73a6d7bb53e53d292534d6af
    if let Ok(ResultErrorResponse {
                  result: Some(tradable_assets),
                  ..
              }) = open_orders_response
    {
        for (asset, details) in tradable_assets {
            println!("{asset}: {details:?}")
        }
    }
}

Example: Calling a Private Endpoint

Private endpoint calls require valid credentials, which can be provided statically by any means, or via an EnvSecretsProvider that will automatically load an .env file in the project directory, and retrieve the specified keys from the local env. See the full example for imports.

#[tokio::main]
async fn main() {
    // note that this will fail if you don't have your key and secret set to these env vars
    // eg `export KRAKEN_KEY="YOUR-API-KEY"`, ...
    let secrets_provider: Arc<Mutex<Box<dyn SecretsProvider>>> = Box::new(Arc::new(Mutex::new(StaticSecretsProvider::new("", ""))));
    let nonce_provider: Arc<Mutex<Box<dyn NonceProvider>>> =
        Arc::new(Mutex::new(Box::new(IncreasingNonceProvider::new())));
    let mut client = CoreKrakenClient::new(secrets_provider, nonce_provider);

    let request = OpenOrdersRequest::builder().build();

    let open_orders_response = client.get_open_orders(&request).await;

    if let Ok(ResultErrorResponse {
                  result: Some(open_orders),
                  ..
              }) = open_orders_response
    {
        for (order_id, order) in open_orders.open {
            println!("{order_id}: {order:?}")
        }
    }
}

Example: Listening to Websockets (V2)

Public websockets require no authentication, so it's as easy as creating a v2::KrakenWSSClient, connecting, and sending any subscription methods and then awaiting the .next() method of the returned KrakenMessageStream.

You can also visit the full example with logging and imports.

#[tokio::main]
async fn main() {
    let mut client = KrakenWSSClient::new();
    let mut kraken_stream = client.connect::<WssMessage>().await.unwrap();

    let ohlc_params = OhlcSubscription::new(vec!["ETH/USD".into()], 60);
    let subscription = Message::new_subscription(ohlc_params, 0);

    let result = kraken_stream.send(&subscription).await;
    assert!(result.is_ok());

    while let Ok(Some(message)) = timeout(Duration::from_secs(10), kraken_stream.next()).await {
        if let Ok(response) = message {
            println!("{:?}", response);
        } else {
            println!("Message failed: {:?}", message);
        }
    }
}

Request Details

Requests that have more than 1 or 2 parameters are generally given a struct, rather than having methods with many parameters. The builder implementation enforces required parameter by using the simple-builder package that marks fields as required, ensuring they must be provided in the .builder() call. Any optional parameters can be added using a fluent API.

For example, the Depth (orderbook) endpoint requires a pair, but can optionally take a count parameter for the number of bids/asks on each side to return. The builder then behaves like below:

let request = OrderbookRequest::builder("ETHUSD".to_string())
        .count(500)
        .build();

Response Details

A best-effort was made to adhere to the format of Kraken's responses, except for cases where it poses some pretty severe usability limitations. Deserialization uses serde, and leaves most datatypes as-is, except Strings are parsed to rust_decimal::Decimal, and many enums are used where the values are clearly documented. The majority of i64 String/RFC3339, and f64 timestamps remain as such. The goal was to provide a great base library for others to build from, without limiting downstream uses by parsing everything and reducing overall performance. If you're developing general-purpose trading algorithms, you should be writing them over a common abstraction that can do this parsing anyway.

If you disagree or have parsing, formatting, or any other issues or blocked use cases, please reach out with a clear example of your issue!

Security

  • The secrecy crate is used to prevent accidental logging of websocket tokens in request and response objects
  • Tracing of requests and responses are off by default, and will log tokens when enabled, as they log incoming and outgoing messages as strings, which cannot be redacted easily.

Stability

  • This crate aims to be a stable base for others to build from, following semantic versioning.
  • Breaking changes will be noted ahead of time in the changelog as much as possible, and will be noted along with an upgrade plan in each version's changelog notes.

Misc Details

  • Parameters and response values are often renamed from the Kraken API fields to adhere to Rust's naming conventions or improve readability[^2]

Contributions

This is a large project developed in isolation, and I undoubtedly missed things despite my best efforts. Please reach out with a clear example of any bugs, usability problems, or suggestions for improvement!

[^1]: An example being the AddOrder endpoint that requires a "Good-'til-Date" order to also have a specified endtm value. Cases like these are numerous and not enforced by this library.

[^2]: Examples include refid -> ref_id, endtm -> end_time, ofs -> order_flags ( or offset...), vol_exec -> executed_volume, qty -> quantity, and many more.

[^3]: NFT trading added some 20+ endpoints near the completion of this library's initial version. I'm considering adding it, but have no use case for it. Reach out if you do, or want to contribute.

Dependencies

~11–23MB
~355K SLoC