rust-okx 0.4.0

Async Rust client for the OKX v5 REST API
Documentation
use crate::OkxClient;
use crate::model::InstType;
use crate::test_util::MockTransport;

#[tokio::test]
async fn get_ticker_builds_request_and_parses() {
    let body = r#"{"code":"0","msg":"","data":[
            {"instType":"SPOT","instId":"BTC-USDT","last":"42000.1","lastSz":"0.1",
             "askPx":"42000.2","askSz":"1","bidPx":"42000.0","bidSz":"2",
             "open24h":"41000","high24h":"43000","low24h":"40000","vol24h":"1000",
             "volCcy24h":"42000000","ts":"1597026383085"}]}"#;
    let mock = MockTransport::new(body);
    let client = OkxClient::with_transport(mock.clone()).build();

    let tickers = client.market().get_ticker("BTC-USDT").await.unwrap();
    assert_eq!(tickers[0].inst_id, "BTC-USDT");
    assert_eq!(tickers[0].last.as_str(), "42000.1");
    assert_eq!(tickers[0].bid_px.parse::<f64>().unwrap(), 42000.0);

    let req = mock.captured();
    assert_eq!(req.method, http::Method::GET);
    assert_eq!(req.query(), Some("instId=BTC-USDT"));
    assert!(!req.is_signed());
}

#[tokio::test]
async fn get_tickers_builds_request_and_parses() {
    let body = r#"{"code":"0","msg":"","data":[
            {"instType":"SWAP","instId":"BTC-USDT-SWAP","last":"42000.1","ts":"1597026383085"}]}"#;
    let mock = MockTransport::new(body);
    let client = OkxClient::with_transport(mock.clone()).build();

    let tickers = client
        .market()
        .get_tickers(crate::model::InstType::Swap, Some("BTC-USDT"))
        .await
        .unwrap();
    assert_eq!(tickers[0].inst_id, "BTC-USDT-SWAP");

    let req = mock.captured();
    assert_eq!(req.method, http::Method::GET);
    assert_eq!(req.query(), Some("instType=SWAP&instFamily=BTC-USDT"));
    assert!(!req.is_signed());
}

#[tokio::test]
async fn get_index_tickers_builds_request_and_parses() {
    let body = r#"{"code":"0","msg":"","data":[
            {"instId":"BTC-USD","idxPx":"42000.1","open24h":"41000","high24h":"43000","low24h":"40000","ts":"1597026383085"}]}"#;
    let mock = MockTransport::new(body);
    let client = OkxClient::with_transport(mock.clone()).build();

    let tickers = client
        .market()
        .get_index_tickers(Some("USD"), None)
        .await
        .unwrap();
    assert_eq!(tickers[0].inst_id, "BTC-USD");
    assert_eq!(tickers[0].idx_px.as_str(), "42000.1");

    let req = mock.captured();
    assert_eq!(req.query(), Some("quoteCcy=USD"));
}

#[tokio::test]
async fn get_orderbook_parses_levels_and_passes_depth() {
    let body = r#"{"code":"0","msg":"","data":[
            {"asks":[["42000.2","1","0","3"]],"bids":[["42000.0","2","0","1"]],
             "ts":"1597026383085"}]}"#;
    let mock = MockTransport::new(body);
    let client = OkxClient::with_transport(mock.clone()).build();

    let books = client
        .market()
        .get_orderbook("BTC-USDT", Some(5))
        .await
        .unwrap();
    let book = &books[0];
    assert_eq!(book.asks[0].price.as_str(), "42000.2");
    assert_eq!(book.asks[0].order_count.as_str(), "3");
    assert_eq!(book.bids[0].size.as_str(), "2");

    let req = mock.captured();
    assert_eq!(req.query(), Some("instId=BTC-USDT&sz=5"));
}

#[tokio::test]
async fn get_candlesticks_parses_array_rows() {
    let body = r#"{"code":"0","msg":"","data":[
            ["1597026383085","42000","43000","41000","42500","100","4250000","4250000","1"]]}"#;
    let mock = MockTransport::new(body);
    let client = OkxClient::with_transport(mock.clone()).build();

    let candles = client
        .market()
        .get_candlesticks("BTC-USDT", Some("1H"), Some(1))
        .await
        .unwrap();
    assert_eq!(candles[0].open.as_str(), "42000");
    assert_eq!(candles[0].close.as_str(), "42500");
    assert_eq!(candles[0].confirm.as_str(), "1");

    let req = mock.captured();
    assert_eq!(req.query(), Some("instId=BTC-USDT&bar=1H&limit=1"));
}

#[tokio::test]
async fn get_history_candlesticks_uses_builder_query() {
    let body = r#"{"code":"0","msg":"","data":[
            ["1597026383085","42000","43000","41000","42500","100","4250000","4250000","1"]]}"#;
    let mock = MockTransport::new(body);
    let client = OkxClient::with_transport(mock.clone()).build();
    let request = super::CandlesticksRequest::new("BTC-USDT")
        .bar("1H")
        .limit(2);

    let candles = client
        .market()
        .get_history_candlesticks(&request)
        .await
        .unwrap();
    assert_eq!(candles[0].high.as_str(), "43000");

    let req = mock.captured();
    assert_eq!(req.query(), Some("instId=BTC-USDT&bar=1H&limit=2"));
    assert!(!req.query().unwrap().contains("after"));
}

#[tokio::test]
async fn get_index_candlesticks_uses_builder_query() {
    let body = r#"{"code":"0","msg":"","data":[
            ["1597026383085","42000","43000","41000","42500","100","4250000","4250000","1"]]}"#;
    let mock = MockTransport::new(body);
    let client = OkxClient::with_transport(mock.clone()).build();
    let request = super::CandlesticksRequest::new("BTC-USD").after("10");

    let candles = client
        .market()
        .get_index_candlesticks(&request)
        .await
        .unwrap();
    assert_eq!(candles[0].low.as_str(), "41000");

    let req = mock.captured();
    assert_eq!(req.query(), Some("instId=BTC-USD&after=10"));
}

#[tokio::test]
async fn get_mark_price_candlesticks_uses_builder_query() {
    let body = r#"{"code":"0","msg":"","data":[
            ["1597026383085","42000","43000","41000","42500","100","4250000","4250000","1"]]}"#;
    let mock = MockTransport::new(body);
    let client = OkxClient::with_transport(mock.clone()).build();
    let request = super::CandlesticksRequest::new("BTC-USDT-SWAP").before("20");

    let candles = client
        .market()
        .get_mark_price_candlesticks(&request)
        .await
        .unwrap();
    assert_eq!(candles[0].close.as_str(), "42500");

    let req = mock.captured();
    assert_eq!(req.query(), Some("instId=BTC-USDT-SWAP&before=20"));
}

#[tokio::test]
async fn get_trades_builds_request_and_parses() {
    let body = r#"{"code":"0","msg":"","data":[
            {"instId":"BTC-USDT","tradeId":"1","px":"42000.1","sz":"0.02","side":"buy","ts":"1597026383085"}]}"#;
    let mock = MockTransport::new(body);
    let client = OkxClient::with_transport(mock.clone()).build();

    let trades = client
        .market()
        .get_trades("BTC-USDT", Some(1))
        .await
        .unwrap();
    assert_eq!(trades[0].trade_id, "1");
    assert_eq!(trades[0].px.as_str(), "42000.1");

    let req = mock.captured();
    assert_eq!(req.query(), Some("instId=BTC-USDT&limit=1"));
}

#[tokio::test]
async fn get_history_trades_uses_builder_query() {
    let body = r#"{"code":"0","msg":"","data":[
            {"instId":"BTC-USDT","tradeId":"1","px":"42000.1","sz":"0.02","side":"sell","ts":"1597026383085"}]}"#;
    let mock = MockTransport::new(body);
    let client = OkxClient::with_transport(mock.clone()).build();
    let request = super::HistoryTradesRequest::new("BTC-USDT")
        .trade_type("1")
        .before("100")
        .limit(1);

    let trades = client.market().get_history_trades(&request).await.unwrap();
    assert_eq!(trades[0].side, "sell");

    let req = mock.captured();
    assert_eq!(
        req.query(),
        Some("instId=BTC-USDT&type=1&before=100&limit=1")
    );
    assert!(!req.query().unwrap().contains("after"));
}

#[tokio::test]
async fn get_platform_24_volume_parses_volume() {
    let body = r#"{"code":"0","msg":"","data":[{"volUsd":"1000","volCny":"7100"}]}"#;
    let mock = MockTransport::new(body);
    let client = OkxClient::with_transport(mock.clone()).build();

    let volume = client.market().get_platform_24_volume().await.unwrap();
    assert_eq!(volume[0].vol_usd.as_str(), "1000");

    let req = mock.captured();
    assert!(req.uri.ends_with("/api/v5/market/platform-24-volume"));
    assert_eq!(req.query(), None);
}

#[tokio::test]
async fn get_index_components_builds_request_and_parses() {
    let body = r#"{"code":"0","msg":"","data":[
            {"index":"BTC-USD","ts":"1597026383085","components":[
                {"exch":"okx","symbol":"BTC-USDT","symPx":"42000","wgt":"1","cnvPx":"42000"}]}]}"#;
    let mock = MockTransport::new(body);
    let client = OkxClient::with_transport(mock.clone()).build();

    let components = client
        .market()
        .get_index_components("BTC-USD")
        .await
        .unwrap();
    assert_eq!(components[0].components[0].symbol, "BTC-USDT");

    let req = mock.captured();
    assert_eq!(req.query(), Some("index=BTC-USD"));
}

#[tokio::test]
async fn get_exchange_rate_parses_rate() {
    let body = r#"{"code":"0","msg":"","data":[{"usdCny":"7.1"}]}"#;
    let mock = MockTransport::new(body);
    let client = OkxClient::with_transport(mock.clone()).build();

    let rate = client.market().get_exchange_rate().await.unwrap();
    assert_eq!(rate[0].usd_cny.as_str(), "7.1");

    let req = mock.captured();
    assert!(req.uri.ends_with("/api/v5/market/exchange-rate"));
    assert_eq!(req.query(), None);
}

#[tokio::test]
async fn get_block_ticker_builds_query() {
    let body = r#"{"code":"0","msg":"","data":[
            {"instType":"SPOT","instId":"BTC-USDT","last":"42000","ts":"1597026383085"}]}"#;
    let mock = MockTransport::new(body);
    let client = OkxClient::with_transport(mock.clone()).build();

    let ticker = client.market().get_block_ticker("BTC-USDT").await.unwrap();
    assert_eq!(ticker[0].inst_id, "BTC-USDT");

    let req = mock.captured();
    assert!(
        req.uri
            .ends_with("/api/v5/market/block-ticker?instId=BTC-USDT")
    );
}

#[tokio::test]
async fn get_block_tickers_builds_filter_query() {
    let body = r#"{"code":"0","msg":"","data":[
            {"instType":"SWAP","instId":"BTC-USDT-SWAP","ts":"1597026383085"}]}"#;
    let mock = MockTransport::new(body);
    let client = OkxClient::with_transport(mock.clone()).build();

    let ticker = client
        .market()
        .get_block_tickers(InstType::Swap, Some("BTC-USDT"))
        .await
        .unwrap();
    assert_eq!(ticker[0].inst_id, "BTC-USDT-SWAP");
    assert_eq!(ticker[0].last.as_str(), "");

    let req = mock.captured();
    assert_eq!(req.query(), Some("instType=SWAP&instFamily=BTC-USDT"));
}

#[tokio::test]
async fn get_option_instrument_family_trades_builds_query() {
    let body = r#"{"code":"0","msg":"","data":[
            {"instId":"BTC-USD-240628-50000-C","tradeId":"1","px":"10","sz":"1","side":"sell","ts":"1597026383085"}]}"#;
    let mock = MockTransport::new(body);
    let client = OkxClient::with_transport(mock.clone()).build();

    let trades = client
        .market()
        .get_option_instrument_family_trades("BTC-USD")
        .await
        .unwrap();
    assert_eq!(trades[0].side, "sell");

    let req = mock.captured();
    assert!(
        req.uri
            .ends_with("/api/v5/market/option/instrument-family-trades?instFamily=BTC-USD")
    );
}