1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
use super::utils::http_get;
use crate::error::Result;
use std::collections::HashMap;

const BASE_URL: &str = "https://www.okex.com/api";

/// The REST client for OKEx.
///
/// OKEx has Spot, Future, Swap and Option markets.
///
/// * WebSocket API doc: <https://www.okex.com/docs/en/>
/// * Trading at:
///     * Spot <https://www.bitmex.com/app/trade/>
///     * Future <https://www.okex.com/derivatives/futures>
///     * Swap <https://www.okex.com/derivatives/swap>
///     * Option <https://www.okex.com/derivatives/options>
pub struct OkexRestClient {
    _api_key: Option<String>,
    _api_secret: Option<String>,
}

impl OkexRestClient {
    pub fn new(api_key: Option<String>, api_secret: Option<String>) -> Self {
        OkexRestClient {
            _api_key: api_key,
            _api_secret: api_secret,
        }
    }

    /// Get most recent trades.
    ///
    /// 100 trades are returned.
    ///
    /// For example: <https://www.okex.com/api/spot/v3/instruments/BTC-USDT/trades>
    pub fn fetch_trades(symbol: &str) -> Result<String> {
        gen_api!(format!(
            "/{}/v3/instruments/{}/trades",
            pair_to_market_type(symbol),
            symbol
        ))
    }

    /// Get the latest Level2 snapshot of orderbook.
    ///
    /// Top 200 bids and asks are returned.
    ///
    /// For example: <https://www.okex.com/api/spot/v3/instruments/BTC-USDT/book?size=200>,
    // <https://www.okex.com/api/swap/v3/instruments/BTC-USDT-SWAP/depth?size=200>
    pub fn fetch_l2_snapshot(symbol: &str) -> Result<String> {
        gen_api!(format!(
            "/{}/v3/instruments/{}/{}?size=200",
            pair_to_market_type(symbol),
            symbol,
            if symbol.ends_with("-SWAP") {
                "depth"
            } else {
                "book"
            }
        ))
    }

    /// Get option underlying.
    pub fn fetch_option_underlying() -> Result<Vec<String>> {
        let txt = http_get(
            "https://www.okex.com/api/option/v3/underlying",
            &HashMap::new(),
        )?;
        let underlying_indexes = serde_json::from_str::<Vec<String>>(&txt)?;
        Ok(underlying_indexes)
    }
}

fn pair_to_market_type(pair: &str) -> &'static str {
    if pair.ends_with("-SWAP") {
        "swap"
    } else {
        let c = pair.matches('-').count();
        if c == 1 {
            "spot"
        } else if c == 2 {
            let date = &pair[(pair.len() - 6)..];
            debug_assert!(date.parse::<i64>().is_ok());
            "futures"
        } else {
            debug_assert!(pair.ends_with("-C") || pair.ends_with("-P"));
            "option"
        }
    }
}

#[cfg(test)]
mod tests {
    #[test]
    fn test_pair_to_market_type() {
        assert_eq!("spot", super::pair_to_market_type("BTC-USDT"));
        assert_eq!("futures", super::pair_to_market_type("BTC-USDT-210625"));
        assert_eq!("swap", super::pair_to_market_type("BTC-USDT-SWAP"));
        assert_eq!(
            "option",
            super::pair_to_market_type("BTC-USD-210625-72000-C")
        );
    }
}