crypto-msg-parser 1.4.1

Parse websocket messages from cryptocurreny exchanges
Documentation
mod utils;

#[cfg(test)]
mod trade {
    use crypto_msg_parser::{parse_trade, MarketType, TradeSide};
    use float_cmp::approx_eq;

    #[test]
    fn inverse_swap() {
        let raw_msg = r#"{"table":"trade","action":"insert","data":[{"timestamp":"2021-03-12T02:00:04.608Z","symbol":"XBTUSD","side":"Sell","size":900,"price":56927,"tickDirection":"MinusTick","trdMatchID":"d1b82d61-d902-349c-936c-2588b8204aff","grossValue":1581300,"homeNotional":0.015813,"foreignNotional":900}]}"#;
        let trade = &parse_trade("bitmex", MarketType::InverseSwap, raw_msg).unwrap()[0];

        crate::utils::check_trade_fields(
            "bitmex",
            MarketType::InverseSwap,
            "BTC/USD".to_string(),
            trade,
        );

        assert_eq!(trade.quantity_base, 0.015813);
        assert_eq!(trade.quantity_quote, 900.0);
        assert_eq!(trade.quantity_contract, Some(900.0));
        assert_eq!(trade.side, TradeSide::Sell);
    }

    #[test]
    fn quanto_swap() {
        let raw_msg = r#"{"table":"trade","action":"partial","data":[{"timestamp":"2021-03-21T00:22:09.258Z","symbol":"ETHUSD","side":"Buy","size":1,"price":1811.6,"tickDirection":"ZeroPlusTick","trdMatchID":"46fcd532-c20e-ac2c-eaed-392f2d599487","grossValue":181160,"homeNotional":0.058513750731421885,"foreignNotional":106.00351082504389}]}"#;
        let trade = &parse_trade("bitmex", MarketType::QuantoSwap, raw_msg).unwrap()[0];

        crate::utils::check_trade_fields(
            "bitmex",
            MarketType::QuantoSwap,
            "ETH/USD".to_string(),
            trade,
        );

        assert_eq!(trade.quantity_base, 0.058513750731421885);
        assert!(approx_eq!(
            f64,
            trade.quantity_quote,
            106.00351082504389,
            ulps = 12
        ));
        assert_eq!(trade.quantity_contract, Some(1.0));
        assert_eq!(trade.side, TradeSide::Buy);
    }

    #[test]
    fn inverse_future() {
        let raw_msg = r#"{"table":"trade","action":"partial","data":[{"timestamp":"2021-03-21T01:12:42.361Z","symbol":"XBTM21","side":"Sell","size":8000,"price":62695.5,"tickDirection":"ZeroPlusTick","trdMatchID":"68624a99-e949-33cd-d7e9-63307cf15cfc","grossValue":12760000,"homeNotional":0.1276,"foreignNotional":8000}]}"#;
        let trade = &parse_trade("bitmex", MarketType::InverseFuture, raw_msg).unwrap()[0];

        crate::utils::check_trade_fields(
            "bitmex",
            MarketType::InverseFuture,
            "BTC/USD".to_string(),
            trade,
        );

        assert_eq!(trade.quantity_base, 0.1276);
        assert_eq!(trade.quantity_quote, 8000.0);
        assert_eq!(trade.quantity_contract, Some(8000.0));
        assert_eq!(trade.side, TradeSide::Sell);
    }

    #[test]
    fn linear_future() {
        let raw_msg = r#"{"table":"trade","action":"insert","data":[{"timestamp":"2021-03-12T01:46:03.886Z","symbol":"ETHH21","side":"Buy","size":1,"price":0.03191,"tickDirection":"PlusTick","trdMatchID":"a9371640-78d6-53d9-c9e4-31f7b7afb06d","grossValue":3191000,"homeNotional":1,"foreignNotional":0.03191}]}"#;
        let trade = &parse_trade("bitmex", MarketType::LinearFuture, raw_msg).unwrap()[0];

        crate::utils::check_trade_fields(
            "bitmex",
            MarketType::LinearFuture,
            "ETH/BTC".to_string(),
            trade,
        );

        assert_eq!(trade.quantity_base, 1.0);
        assert_eq!(trade.quantity_quote, 0.03191);
        assert_eq!(trade.quantity_contract, Some(1.0));
        assert_eq!(trade.side, TradeSide::Buy);
    }

    #[test]
    fn quanto_future() {
        let raw_msg = r#"{"table":"trade","action":"insert","data":[{"timestamp":"2021-03-12T02:13:43.222Z","symbol":"ETHUSDH21","side":"Sell","size":12,"price":1892.8,"tickDirection":"PlusTick","trdMatchID":"14c7d828-80c4-2c91-ad9e-1662081aeaec","grossValue":2271360,"homeNotional":0.6814310051107325,"foreignNotional":1289.8126064735945}]}"#;
        let trade = &parse_trade("bitmex", MarketType::QuantoFuture, raw_msg).unwrap()[0];

        crate::utils::check_trade_fields(
            "bitmex",
            MarketType::QuantoFuture,
            "ETH/USD".to_string(),
            trade,
        );

        assert_eq!(trade.quantity_base, 0.6814310051107325);
        assert!(approx_eq!(
            f64,
            trade.quantity_quote,
            1289.8126064735945,
            ulps = 12
        ));
        assert_eq!(trade.quantity_contract, Some(12.0));
        assert!(approx_eq!(
            f64,
            trade.quantity_quote,
            trade.price * trade.quantity_base,
            ulps = 12
        ));
        assert_eq!(trade.side, TradeSide::Sell);
    }
}

#[cfg(test)]
mod funding_rate {
    use crypto_msg_parser::{parse_funding_rate, MarketType};

    #[test]
    fn inverse_swap() {
        let raw_msg = r#"{"table":"funding","action":"partial","data":[{"timestamp":"2021-04-01T20:00:00.000Z","symbol":"XBTUSD","fundingInterval":"2000-01-01T08:00:00.000Z","fundingRate":0.000817,"fundingRateDaily":0.002451}]}"#;
        let funding_rates =
            &parse_funding_rate("bitmex", MarketType::InverseSwap, raw_msg).unwrap();

        assert_eq!(funding_rates.len(), 1);

        for rate in funding_rates.iter() {
            crate::utils::check_funding_rate_fields("bitmex", MarketType::InverseSwap, rate);
        }

        assert_eq!(funding_rates[0].pair, "BTC/USD".to_string());
        assert_eq!(funding_rates[0].funding_rate, 0.000817);
        assert_eq!(funding_rates[0].funding_time, 1617307200000);
    }

    #[test]
    fn quanto_swap() {
        let raw_msg = r#"{"table":"funding","action":"partial","data":[{"timestamp":"2021-04-01T20:00:00.000Z","symbol":"ETHUSD","fundingInterval":"2000-01-01T08:00:00.000Z","fundingRate":0.002142,"fundingRateDaily":0.006425999999999999}]}"#;
        let funding_rates = &parse_funding_rate("bitmex", MarketType::QuantoSwap, raw_msg).unwrap();

        assert_eq!(funding_rates.len(), 1);

        for rate in funding_rates.iter() {
            crate::utils::check_funding_rate_fields("bitmex", MarketType::QuantoSwap, rate);
        }

        assert_eq!(funding_rates[0].pair, "ETH/USD".to_string());
        assert_eq!(funding_rates[0].funding_rate, 0.002142);
        assert_eq!(funding_rates[0].funding_time, 1617307200000);
    }
}

#[cfg(test)]
mod l2_orderbook {
    use chrono::prelude::*;
    use crypto_msg_parser::{parse_l2, MarketType};

    #[test]
    fn inverse_swap_snapshot() {
        let raw_msg = r#"{"table":"orderBookL2_25","action":"partial","data":[{"symbol":"XBTUSD","id":8796381000,"side":"Sell","size":49900,"price":36190},{"symbol":"XBTUSD","id":8796381050,"side":"Sell","size":125714,"price":36189.5},{"symbol":"XBTUSD","id":8796381100,"side":"Sell","size":34600,"price":36189},{"symbol":"XBTUSD","id":8796385500,"side":"Buy","size":136,"price":36145},{"symbol":"XBTUSD","id":8796385600,"side":"Buy","size":26,"price":36144},{"symbol":"XBTUSD","id":8796385800,"side":"Buy","size":18067,"price":36142}]}"#;
        let orderbook = &parse_l2(
            "bitmex",
            MarketType::InverseSwap,
            raw_msg,
            Some(Utc::now().timestamp_millis()),
        )
        .unwrap()[0];

        assert_eq!(orderbook.asks.len(), 3);
        assert_eq!(orderbook.bids.len(), 3);
        assert!(orderbook.snapshot);

        crate::utils::check_orderbook_fields(
            "bitmex",
            MarketType::InverseSwap,
            "BTC/USD".to_string(),
            orderbook,
        );

        assert_eq!(orderbook.bids[0].price, 36145.0);
        assert_eq!(orderbook.bids[0].quantity_base, 136.0 / 36145.0);
        assert_eq!(orderbook.bids[0].quantity_quote, 136.0);
        assert_eq!(orderbook.bids[0].quantity_contract.unwrap(), 136.0);

        assert_eq!(orderbook.bids[2].price, 36142.0);
        assert_eq!(orderbook.bids[2].quantity_base, 18067.0 / 36142.0);
        assert_eq!(orderbook.bids[2].quantity_quote, 18067.0);
        assert_eq!(orderbook.bids[2].quantity_contract.unwrap(), 18067.0);

        assert_eq!(orderbook.asks[2].price, 36190.0);
        assert_eq!(orderbook.asks[2].quantity_base, 49900.0 / 36190.0);
        assert_eq!(orderbook.asks[2].quantity_quote, 49900.0);
        assert_eq!(orderbook.asks[2].quantity_contract.unwrap(), 49900.0);

        assert_eq!(orderbook.asks[0].price, 36189.0);
        assert_eq!(orderbook.asks[0].quantity_base, 34600.0 / 36189.0);
        assert_eq!(orderbook.asks[0].quantity_quote, 34600.0);
        assert_eq!(orderbook.asks[0].quantity_contract.unwrap(), 34600.0);
    }

    #[test]
    fn inverse_swap_update() {
        let insert_msg = r#"{"table":"orderBookL2_25","action":"insert","data":[{"symbol":"XBTUSD","id":8796323950,"side":"Sell","size":38760,"price":36760.5}]}"#;
        let _ = parse_l2(
            "bitmex",
            MarketType::InverseSwap,
            insert_msg,
            Some(Utc::now().timestamp_millis()),
        );
        let update_msg = r#"{"table":"orderBookL2_25","action":"update","data":[{"symbol":"XBTUSD","id":8796323950,"side":"Sell","size":36760}]}"#;
        let orderbook = &parse_l2(
            "bitmex",
            MarketType::InverseSwap,
            update_msg,
            Some(Utc::now().timestamp_millis()),
        )
        .unwrap()[0];

        assert_eq!(orderbook.asks.len(), 1);
        assert_eq!(orderbook.bids.len(), 0);
        assert!(!orderbook.snapshot);

        crate::utils::check_orderbook_fields(
            "bitmex",
            MarketType::InverseSwap,
            "BTC/USD".to_string(),
            orderbook,
        );

        assert_eq!(orderbook.asks[0].price, 36760.5);
        assert_eq!(orderbook.asks[0].quantity_base, 36760.0 / 36760.5);
        assert_eq!(orderbook.asks[0].quantity_quote, 36760.0);
        assert_eq!(orderbook.asks[0].quantity_contract.unwrap(), 36760.0);

        let delete_msg = r#"{"table":"orderBookL2_25","action":"delete","data":[{"symbol":"XBTUSD","id":8796323950,"side":"Sell"}]}"#;
        let orderbook = &parse_l2(
            "bitmex",
            MarketType::InverseSwap,
            delete_msg,
            Some(Utc::now().timestamp_millis()),
        )
        .unwrap()[0];

        assert_eq!(orderbook.asks.len(), 1);
        assert_eq!(orderbook.bids.len(), 0);
        assert!(!orderbook.snapshot);

        crate::utils::check_orderbook_fields(
            "bitmex",
            MarketType::InverseSwap,
            "BTC/USD".to_string(),
            orderbook,
        );

        assert_eq!(orderbook.asks[0].price, 36760.5);
        assert_eq!(orderbook.asks[0].quantity_base, 0.0);
        assert_eq!(orderbook.asks[0].quantity_quote, 0.0);
        assert_eq!(orderbook.asks[0].quantity_contract.unwrap(), 0.0);
    }
}