use async_trait::async_trait;
use std::fmt;
use crate::Decimal;
use crate::types::error::MMResult;
#[cfg(feature = "serde")]
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct OrderId(String);
impl OrderId {
#[must_use]
pub fn new(id: impl Into<String>) -> Self {
Self(id.into())
}
#[must_use]
pub fn as_str(&self) -> &str {
&self.0
}
#[must_use]
pub fn into_inner(self) -> String {
self.0
}
}
impl fmt::Display for OrderId {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.0)
}
}
impl From<String> for OrderId {
fn from(s: String) -> Self {
Self(s)
}
}
impl From<&str> for OrderId {
fn from(s: &str) -> Self {
Self(s.to_string())
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub enum Side {
Buy,
Sell,
}
impl Side {
#[must_use]
pub fn is_buy(&self) -> bool {
matches!(self, Side::Buy)
}
#[must_use]
pub fn is_sell(&self) -> bool {
matches!(self, Side::Sell)
}
#[must_use]
pub fn opposite(&self) -> Self {
match self {
Side::Buy => Side::Sell,
Side::Sell => Side::Buy,
}
}
}
impl fmt::Display for Side {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Side::Buy => write!(f, "Buy"),
Side::Sell => write!(f, "Sell"),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub enum OrderType {
Limit,
Market,
PostOnly,
}
impl OrderType {
#[must_use]
pub fn requires_price(&self) -> bool {
matches!(self, OrderType::Limit | OrderType::PostOnly)
}
#[must_use]
pub fn is_market(&self) -> bool {
matches!(self, OrderType::Market)
}
}
impl fmt::Display for OrderType {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
OrderType::Limit => write!(f, "Limit"),
OrderType::Market => write!(f, "Market"),
OrderType::PostOnly => write!(f, "PostOnly"),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[derive(Default)]
pub enum TimeInForce {
#[default]
GoodTilCancel,
ImmediateOrCancel,
FillOrKill,
GoodTilTime(u64),
}
impl TimeInForce {
#[must_use]
pub fn is_immediate(&self) -> bool {
matches!(
self,
TimeInForce::ImmediateOrCancel | TimeInForce::FillOrKill
)
}
}
impl fmt::Display for TimeInForce {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
TimeInForce::GoodTilCancel => write!(f, "GTC"),
TimeInForce::ImmediateOrCancel => write!(f, "IOC"),
TimeInForce::FillOrKill => write!(f, "FOK"),
TimeInForce::GoodTilTime(ts) => write!(f, "GTT({})", ts),
}
}
}
#[derive(Debug, Clone, PartialEq)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub enum OrderStatus {
Pending,
Open {
filled_qty: Decimal,
},
PartiallyFilled {
filled_qty: Decimal,
remaining_qty: Decimal,
},
Filled {
filled_qty: Decimal,
avg_price: Decimal,
},
Cancelled {
filled_qty: Decimal,
},
Rejected {
reason: String,
},
}
impl OrderStatus {
#[must_use]
pub fn is_active(&self) -> bool {
matches!(
self,
OrderStatus::Pending | OrderStatus::Open { .. } | OrderStatus::PartiallyFilled { .. }
)
}
#[must_use]
pub fn is_open(&self) -> bool {
matches!(
self,
OrderStatus::Open { .. } | OrderStatus::PartiallyFilled { .. }
)
}
#[must_use]
pub fn is_terminal(&self) -> bool {
matches!(
self,
OrderStatus::Filled { .. }
| OrderStatus::Cancelled { .. }
| OrderStatus::Rejected { .. }
)
}
#[must_use]
pub fn filled_qty(&self) -> Decimal {
match self {
OrderStatus::Pending => Decimal::ZERO,
OrderStatus::Open { filled_qty } => *filled_qty,
OrderStatus::PartiallyFilled { filled_qty, .. } => *filled_qty,
OrderStatus::Filled { filled_qty, .. } => *filled_qty,
OrderStatus::Cancelled { filled_qty } => *filled_qty,
OrderStatus::Rejected { .. } => Decimal::ZERO,
}
}
}
impl fmt::Display for OrderStatus {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
OrderStatus::Pending => write!(f, "Pending"),
OrderStatus::Open { filled_qty } => write!(f, "Open(filled={})", filled_qty),
OrderStatus::PartiallyFilled {
filled_qty,
remaining_qty,
} => write!(
f,
"PartiallyFilled(filled={}, remaining={})",
filled_qty, remaining_qty
),
OrderStatus::Filled {
filled_qty,
avg_price,
} => {
write!(f, "Filled(qty={}, avg_price={})", filled_qty, avg_price)
}
OrderStatus::Cancelled { filled_qty } => write!(f, "Cancelled(filled={})", filled_qty),
OrderStatus::Rejected { reason } => write!(f, "Rejected({})", reason),
}
}
}
#[derive(Debug, Clone, PartialEq)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct OrderRequest {
pub symbol: String,
pub side: Side,
pub order_type: OrderType,
pub price: Option<Decimal>,
pub quantity: Decimal,
pub time_in_force: TimeInForce,
pub client_order_id: Option<String>,
}
impl OrderRequest {
#[must_use]
pub fn new(
symbol: impl Into<String>,
side: Side,
order_type: OrderType,
price: Option<Decimal>,
quantity: Decimal,
) -> Self {
Self {
symbol: symbol.into(),
side,
order_type,
price,
quantity,
time_in_force: TimeInForce::default(),
client_order_id: None,
}
}
#[must_use]
pub fn limit_buy(symbol: impl Into<String>, price: Decimal, quantity: Decimal) -> Self {
Self::new(symbol, Side::Buy, OrderType::Limit, Some(price), quantity)
}
#[must_use]
pub fn limit_sell(symbol: impl Into<String>, price: Decimal, quantity: Decimal) -> Self {
Self::new(symbol, Side::Sell, OrderType::Limit, Some(price), quantity)
}
#[must_use]
pub fn market_buy(symbol: impl Into<String>, quantity: Decimal) -> Self {
Self::new(symbol, Side::Buy, OrderType::Market, None, quantity)
}
#[must_use]
pub fn market_sell(symbol: impl Into<String>, quantity: Decimal) -> Self {
Self::new(symbol, Side::Sell, OrderType::Market, None, quantity)
}
#[must_use]
pub fn with_time_in_force(mut self, tif: TimeInForce) -> Self {
self.time_in_force = tif;
self
}
#[must_use]
pub fn with_client_order_id(mut self, id: impl Into<String>) -> Self {
self.client_order_id = Some(id.into());
self
}
#[must_use]
pub fn notional(&self) -> Option<Decimal> {
self.price.map(|p| p * self.quantity)
}
}
#[derive(Debug, Clone, PartialEq)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct OrderResponse {
pub order_id: OrderId,
pub client_order_id: Option<String>,
pub status: OrderStatus,
pub timestamp: u64,
}
impl OrderResponse {
#[must_use]
pub fn new(order_id: OrderId, status: OrderStatus, timestamp: u64) -> Self {
Self {
order_id,
client_order_id: None,
status,
timestamp,
}
}
#[must_use]
pub fn with_client_order_id(mut self, id: impl Into<String>) -> Self {
self.client_order_id = Some(id.into());
self
}
}
#[derive(Debug, Clone, PartialEq)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct Fill {
pub order_id: OrderId,
pub trade_id: String,
pub price: Decimal,
pub quantity: Decimal,
pub side: Side,
pub timestamp: u64,
pub fee: Decimal,
pub fee_currency: String,
}
impl Fill {
#[must_use]
pub fn notional(&self) -> Decimal {
self.price * self.quantity
}
#[must_use]
pub fn net_value(&self) -> Decimal {
match self.side {
Side::Buy => self.notional() + self.fee,
Side::Sell => self.notional() - self.fee,
}
}
}
#[derive(Debug, Clone, PartialEq)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct BookLevel {
pub price: Decimal,
pub quantity: Decimal,
}
impl BookLevel {
#[must_use]
pub fn new(price: Decimal, quantity: Decimal) -> Self {
Self { price, quantity }
}
#[must_use]
pub fn notional(&self) -> Decimal {
self.price * self.quantity
}
}
#[derive(Debug, Clone, PartialEq)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct OrderBookSnapshot {
pub symbol: String,
pub bids: Vec<BookLevel>,
pub asks: Vec<BookLevel>,
pub timestamp: u64,
}
impl OrderBookSnapshot {
#[must_use]
pub fn new(symbol: impl Into<String>, timestamp: u64) -> Self {
Self {
symbol: symbol.into(),
bids: Vec::new(),
asks: Vec::new(),
timestamp,
}
}
#[must_use]
pub fn best_bid(&self) -> Option<Decimal> {
self.bids.first().map(|l| l.price)
}
#[must_use]
pub fn best_ask(&self) -> Option<Decimal> {
self.asks.first().map(|l| l.price)
}
#[must_use]
pub fn mid_price(&self) -> Option<Decimal> {
match (self.best_bid(), self.best_ask()) {
(Some(bid), Some(ask)) => Some((bid + ask) / Decimal::from(2)),
_ => None,
}
}
#[must_use]
pub fn spread(&self) -> Option<Decimal> {
match (self.best_bid(), self.best_ask()) {
(Some(bid), Some(ask)) => Some(ask - bid),
_ => None,
}
}
#[must_use]
pub fn spread_bps(&self) -> Option<Decimal> {
match (self.spread(), self.mid_price()) {
(Some(spread), Some(mid)) if mid > Decimal::ZERO => {
Some(spread / mid * Decimal::from(10000))
}
_ => None,
}
}
#[must_use]
pub fn bid_depth(&self) -> Decimal {
self.bids.iter().map(|l| l.quantity).sum()
}
#[must_use]
pub fn ask_depth(&self) -> Decimal {
self.asks.iter().map(|l| l.quantity).sum()
}
#[must_use]
pub fn imbalance(&self) -> Decimal {
let bid_depth = self.bid_depth();
let ask_depth = self.ask_depth();
let total = bid_depth + ask_depth;
if total > Decimal::ZERO {
(bid_depth - ask_depth) / total
} else {
Decimal::ZERO
}
}
}
#[async_trait]
pub trait ExchangeConnector: Send + Sync {
async fn submit_order(&self, request: OrderRequest) -> MMResult<OrderResponse>;
async fn cancel_order(&self, order_id: &OrderId) -> MMResult<OrderResponse>;
async fn modify_order(
&self,
order_id: &OrderId,
new_price: Option<Decimal>,
new_quantity: Option<Decimal>,
) -> MMResult<OrderResponse>;
async fn get_order_status(&self, order_id: &OrderId) -> MMResult<OrderResponse>;
async fn get_open_orders(&self, symbol: &str) -> MMResult<Vec<OrderResponse>>;
async fn cancel_all_orders(&self, symbol: &str) -> MMResult<Vec<OrderResponse>>;
async fn get_orderbook(&self, symbol: &str, depth: usize) -> MMResult<OrderBookSnapshot>;
async fn get_balance(&self, asset: &str) -> MMResult<Decimal>;
}
#[async_trait]
pub trait MarketDataStream: Send + Sync {
async fn subscribe_orderbook(&self, symbol: &str) -> MMResult<()>;
async fn subscribe_trades(&self, symbol: &str) -> MMResult<()>;
async fn next_orderbook_update(&self) -> MMResult<OrderBookSnapshot>;
async fn next_trade(&self) -> MMResult<Fill>;
}
#[cfg(test)]
mod tests {
use super::*;
use crate::dec;
#[test]
fn test_order_id() {
let id = OrderId::new("12345");
assert_eq!(id.as_str(), "12345");
assert_eq!(id.to_string(), "12345");
let id2: OrderId = "67890".into();
assert_eq!(id2.as_str(), "67890");
}
#[test]
fn test_side() {
assert!(Side::Buy.is_buy());
assert!(!Side::Buy.is_sell());
assert!(Side::Sell.is_sell());
assert!(!Side::Sell.is_buy());
assert_eq!(Side::Buy.opposite(), Side::Sell);
assert_eq!(Side::Sell.opposite(), Side::Buy);
}
#[test]
fn test_order_type() {
assert!(OrderType::Limit.requires_price());
assert!(OrderType::PostOnly.requires_price());
assert!(!OrderType::Market.requires_price());
assert!(OrderType::Market.is_market());
}
#[test]
fn test_time_in_force() {
assert!(!TimeInForce::GoodTilCancel.is_immediate());
assert!(TimeInForce::ImmediateOrCancel.is_immediate());
assert!(TimeInForce::FillOrKill.is_immediate());
assert!(!TimeInForce::GoodTilTime(1000).is_immediate());
}
#[test]
fn test_order_status() {
let pending = OrderStatus::Pending;
assert!(pending.is_active());
assert!(!pending.is_open());
assert!(!pending.is_terminal());
assert_eq!(pending.filled_qty(), Decimal::ZERO);
let open = OrderStatus::Open {
filled_qty: dec!(0.5),
};
assert!(open.is_active());
assert!(open.is_open());
assert!(!open.is_terminal());
assert_eq!(open.filled_qty(), dec!(0.5));
let filled = OrderStatus::Filled {
filled_qty: dec!(1.0),
avg_price: dec!(100.0),
};
assert!(!filled.is_active());
assert!(!filled.is_open());
assert!(filled.is_terminal());
assert_eq!(filled.filled_qty(), dec!(1.0));
let rejected = OrderStatus::Rejected {
reason: "test".to_string(),
};
assert!(rejected.is_terminal());
assert_eq!(rejected.filled_qty(), Decimal::ZERO);
}
#[test]
fn test_order_request() {
let request = OrderRequest::new(
"BTC-USD",
Side::Buy,
OrderType::Limit,
Some(dec!(50000.0)),
dec!(0.1),
);
assert_eq!(request.symbol, "BTC-USD");
assert_eq!(request.side, Side::Buy);
assert_eq!(request.order_type, OrderType::Limit);
assert_eq!(request.price, Some(dec!(50000.0)));
assert_eq!(request.quantity, dec!(0.1));
assert_eq!(request.notional(), Some(dec!(5000.0)));
}
#[test]
fn test_order_request_builders() {
let buy = OrderRequest::limit_buy("BTC-USD", dec!(50000.0), dec!(0.1));
assert_eq!(buy.side, Side::Buy);
assert_eq!(buy.order_type, OrderType::Limit);
let sell = OrderRequest::limit_sell("BTC-USD", dec!(51000.0), dec!(0.1));
assert_eq!(sell.side, Side::Sell);
let market_buy = OrderRequest::market_buy("BTC-USD", dec!(0.1));
assert_eq!(market_buy.order_type, OrderType::Market);
assert_eq!(market_buy.price, None);
assert_eq!(market_buy.notional(), None);
}
#[test]
fn test_fill() {
let fill = Fill {
order_id: OrderId::new("12345"),
trade_id: "trade-1".to_string(),
price: dec!(50000.0),
quantity: dec!(0.1),
side: Side::Buy,
timestamp: 1000,
fee: dec!(0.5),
fee_currency: "USD".to_string(),
};
assert_eq!(fill.notional(), dec!(5000.0));
assert_eq!(fill.net_value(), dec!(5000.5));
let sell_fill = Fill {
side: Side::Sell,
..fill.clone()
};
assert_eq!(sell_fill.net_value(), dec!(4999.5)); }
#[test]
fn test_book_level() {
let level = BookLevel::new(dec!(50000.0), dec!(1.5));
assert_eq!(level.notional(), dec!(75000.0));
}
#[test]
fn test_order_book_snapshot() {
let snapshot = OrderBookSnapshot {
symbol: "BTC-USD".to_string(),
bids: vec![
BookLevel::new(dec!(49990.0), dec!(1.0)),
BookLevel::new(dec!(49980.0), dec!(2.0)),
],
asks: vec![
BookLevel::new(dec!(50010.0), dec!(1.0)),
BookLevel::new(dec!(50020.0), dec!(2.0)),
],
timestamp: 1000,
};
assert_eq!(snapshot.best_bid(), Some(dec!(49990.0)));
assert_eq!(snapshot.best_ask(), Some(dec!(50010.0)));
assert_eq!(snapshot.mid_price(), Some(dec!(50000.0)));
assert_eq!(snapshot.spread(), Some(dec!(20.0)));
assert_eq!(snapshot.bid_depth(), dec!(3.0));
assert_eq!(snapshot.ask_depth(), dec!(3.0));
assert_eq!(snapshot.imbalance(), Decimal::ZERO);
}
#[test]
fn test_order_book_imbalance() {
let snapshot = OrderBookSnapshot {
symbol: "BTC-USD".to_string(),
bids: vec![BookLevel::new(dec!(100.0), dec!(3.0))],
asks: vec![BookLevel::new(dec!(101.0), dec!(1.0))],
timestamp: 1000,
};
assert_eq!(snapshot.imbalance(), dec!(0.5));
}
#[test]
fn test_empty_order_book() {
let snapshot = OrderBookSnapshot::new("BTC-USD", 1000);
assert_eq!(snapshot.best_bid(), None);
assert_eq!(snapshot.best_ask(), None);
assert_eq!(snapshot.mid_price(), None);
assert_eq!(snapshot.spread(), None);
assert_eq!(snapshot.imbalance(), Decimal::ZERO);
}
#[cfg(feature = "serde")]
#[test]
fn test_serialization() {
let request = OrderRequest::limit_buy("BTC-USD", dec!(50000.0), dec!(0.1));
let json = serde_json::to_string(&request).unwrap();
let deserialized: OrderRequest = serde_json::from_str(&json).unwrap();
assert_eq!(request, deserialized);
}
}