coinbase-pro-rs 0.8.1

Coinbase pro client for Rust
Documentation
use std::borrow::Cow;
use uuid::Uuid;

use serde::{Deserialize, Serialize};

#[derive(Serialize, Deserialize, Debug)]
pub struct Order<'a> {
    side: OrderSide,
    client_oid: Option<Uuid>,
    product_id: Cow<'a, str>,
    #[serde(flatten)]
    _type: OrderType,
    #[serde(flatten)]
    stop: Option<OrderStop>,
}

#[derive(Serialize, Deserialize, Debug, PartialEq, Clone, Copy)]
#[serde(rename_all = "camelCase")]
pub enum OrderSide {
    Buy,
    Sell,
}

#[derive(Serialize, Deserialize, Debug)]
#[serde(tag = "type")]
#[serde(rename_all = "camelCase")]
pub enum OrderType {
    Limit {
        price: f64,
        size: f64,
        post_only: bool,
        #[serde(flatten)]
        time_in_force: Option<OrderTimeInForce>,
    },
    Market {
        #[serde(flatten)]
        _type: MarketType,
    },
}

#[derive(Serialize, Deserialize, Debug, Clone)]
#[serde(untagged)]
#[serde(rename_all = "camelCase")]
pub enum MarketType {
    Size { size: f64 },
    Funds { funds: f64 },
}

impl<'a> Order<'a> {
    pub fn market<T: Into<Cow<'a, str>>>(product_id: T, side: OrderSide, size: f64) -> Self {
        Order {
            product_id: product_id.into(),
            client_oid: None,
            side,
            _type: OrderType::Market {
                _type: MarketType::Size { size },
            },
            stop: None,
        }
    }

    pub fn buy_market<T: Into<Cow<'a, str>>>(product_id: T, size: f64) -> Self {
        Self::market(product_id, OrderSide::Buy, size)
    }

    pub fn sell_market<T: Into<Cow<'a, str>>>(product_id: T, size: f64) -> Self {
        Self::market(product_id, OrderSide::Sell, size)
    }

    pub fn market_funds<T: Into<Cow<'a, str>>>(product_id: T, side: OrderSide, funds: f64) -> Self {
        Order {
            product_id: product_id.into(),
            client_oid: None,
            side,
            _type: OrderType::Market {
                _type: MarketType::Funds { funds },
            },
            stop: None,
        }
    }

    pub fn buy_market_funds<T: Into<Cow<'a, str>>>(product_id: T, funds: f64) -> Self {
        Self::market_funds(product_id, OrderSide::Buy, funds)
    }

    pub fn sell_market_funds<T: Into<Cow<'a, str>>>(product_id: T, funds: f64) -> Self {
        Self::market_funds(product_id, OrderSide::Sell, funds)
    }

    pub fn limit<T: Into<Cow<'a, str>>>(
        product_id: T,
        side: OrderSide,
        size: f64,
        price: f64,
        post_only: bool,
    ) -> Self {
        Order {
            product_id: product_id.into(),
            client_oid: None,
            side,
            _type: OrderType::Limit {
                price,
                size,
                post_only,
                time_in_force: None,
            },
            stop: None,
        }
    }

    pub fn buy_limit<T: Into<Cow<'a, str>>>(
        product_id: T,
        size: f64,
        price: f64,
        post_only: bool,
    ) -> Self {
        Self::limit(product_id, OrderSide::Buy, size, price, post_only)
    }

    pub fn sell_limit<T: Into<Cow<'a, str>>>(
        product_id: T,
        size: f64,
        price: f64,
        post_only: bool,
    ) -> Self {
        Self::limit(product_id, OrderSide::Sell, size, price, post_only)
    }

    pub fn client_oid(self, client_oid: Uuid) -> Self {
        let client_oid = Some(client_oid);
        Order { client_oid, ..self }
    }

    pub fn stop(self, price: f64, stop_type: OrderStopType) -> Self {
        let stop = Some(OrderStop {
            stop_price: price,
            _type: stop_type,
        });
        Order { stop, ..self }
    }

    pub fn stop_loss(self, price: f64) -> Self {
        self.stop(price, OrderStopType::Loss)
    }

    pub fn stop_entry(self, price: f64) -> Self {
        self.stop(price, OrderStopType::Entry)
    }

    pub fn time_in_force(self, time_in_force: OrderTimeInForce) -> Self {
        match self._type {
            OrderType::Limit {
                price,
                size,
                post_only,
                ..
            } => {
                let _type = OrderType::Limit {
                    price,
                    size,
                    post_only,
                    time_in_force: Some(time_in_force),
                };
                Order { _type, ..self }
            }
            _ => panic!("time_in_force is for limit orders only"),
        }
    }
}

#[derive(Serialize, Deserialize, Debug, Clone)]
#[serde(tag = "time_in_force")]
pub enum OrderTimeInForce {
    GTC,
    GTT {
        cancel_after: OrderTimeInForceCancelAfter,
    },
    IOC,
    FOK,
}

#[derive(Serialize, Deserialize, Debug, Clone, Copy)]
#[serde(rename_all = "camelCase")]
pub enum OrderTimeInForceCancelAfter {
    Min,
    Hour,
    Day,
}

#[derive(Serialize, Deserialize, Debug)]
pub struct OrderStop {
    stop_price: f64,
    #[serde(rename = "stop")]
    _type: OrderStopType,
}

#[derive(Serialize, Deserialize, Debug, Clone, Copy)]
#[serde(rename_all = "camelCase")]
pub enum OrderStopType {
    Loss,
    Entry,
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_order_builder() {
        let o = Order::buy_limit("BTC-USD", 10.0, 100.0, true);
        assert!(o.client_oid.is_none());

        match &o._type {
            OrderType::Limit {
                time_in_force: None,
                ..
            } => assert!(true),
            _ => assert!(false),
        }

        let o = Order::buy_limit("BTC-USD", 10.0, 100.0, true)
            .client_oid(Uuid::nil())
            .stop_loss(99.0)
            .time_in_force(OrderTimeInForce::GTC);
        assert!(o.client_oid.is_some());
        assert!(o.stop.is_some());

        match &o._type {
            OrderType::Limit {
                time_in_force: Some(OrderTimeInForce::GTC),
                ..
            } => assert!(true),
            _ => assert!(false),
        }
    }

    #[derive(Debug)]
    enum Coin {
        AAA,
        BBB,
    }
    #[derive(Debug)]
    struct Pair {
        a: Coin,
        b: Coin,
    }

    impl<'a> From<Pair> for Order<'a> {
        fn from(pair: Pair) -> Self {
            Order::buy_market(format!("{:?}-{:?}", pair.a, pair.b), 10.0)
        }
    }

    #[test]
    fn test_order_from() {
        let p = Pair {
            a: Coin::AAA,
            b: Coin::BBB,
        };
        let order_owned: Order = p.into();
        assert_eq!(order_owned.product_id, "AAA-BBB");
        let order_str: Order = Order::buy_market("AAA-BBB", 10.0);
        assert_eq!(order_str.product_id, "AAA-BBB");
    }
}