use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
#[serde(rename_all = "lowercase")]
pub enum OrderSide {
Buy,
Sell,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
#[serde(rename_all = "snake_case")]
pub enum OrderType {
Market,
Limit,
StopLoss,
StopLossLimit,
TakeProfit,
TakeProfitLimit,
TrailingStop,
TrailingStopLimit,
Iceberg,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
#[serde(rename_all = "lowercase")]
pub enum TimeInForce {
GTC,
IOC,
FOK,
GTD,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
#[serde(rename_all = "snake_case")]
pub enum SelfTradePrevention {
CancelNewest,
CancelOldest,
CancelBoth,
}
#[derive(Debug, Clone, Serialize)]
pub struct OrderParams {
pub symbol: String,
pub side: OrderSide,
pub order_type: OrderType,
#[serde(skip_serializing_if = "Option::is_none")]
pub order_qty: Option<f64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub limit_price: Option<f64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub trigger_price: Option<f64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub time_in_force: Option<TimeInForce>,
#[serde(skip_serializing_if = "Option::is_none")]
pub post_only: Option<bool>,
#[serde(skip_serializing_if = "Option::is_none")]
pub reduce_only: Option<bool>,
#[serde(skip_serializing_if = "Option::is_none")]
pub stp: Option<SelfTradePrevention>,
#[serde(skip_serializing_if = "Option::is_none")]
pub cl_ord_id: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub validate: Option<bool>,
}
impl OrderParams {
pub fn market_buy(symbol: impl Into<String>, quantity: f64) -> Self {
Self {
symbol: symbol.into(),
side: OrderSide::Buy,
order_type: OrderType::Market,
order_qty: Some(quantity),
limit_price: None,
trigger_price: None,
time_in_force: None,
post_only: None,
reduce_only: None,
stp: None,
cl_ord_id: None,
validate: None,
}
}
pub fn market_sell(symbol: impl Into<String>, quantity: f64) -> Self {
Self {
symbol: symbol.into(),
side: OrderSide::Sell,
order_type: OrderType::Market,
order_qty: Some(quantity),
limit_price: None,
trigger_price: None,
time_in_force: None,
post_only: None,
reduce_only: None,
stp: None,
cl_ord_id: None,
validate: None,
}
}
pub fn limit_buy(symbol: impl Into<String>, quantity: f64, price: f64) -> Self {
Self {
symbol: symbol.into(),
side: OrderSide::Buy,
order_type: OrderType::Limit,
order_qty: Some(quantity),
limit_price: Some(price),
trigger_price: None,
time_in_force: None,
post_only: None,
reduce_only: None,
stp: None,
cl_ord_id: None,
validate: None,
}
}
pub fn limit_sell(symbol: impl Into<String>, quantity: f64, price: f64) -> Self {
Self {
symbol: symbol.into(),
side: OrderSide::Sell,
order_type: OrderType::Limit,
order_qty: Some(quantity),
limit_price: Some(price),
trigger_price: None,
time_in_force: None,
post_only: None,
reduce_only: None,
stp: None,
cl_ord_id: None,
validate: None,
}
}
pub fn with_time_in_force(mut self, tif: TimeInForce) -> Self {
self.time_in_force = Some(tif);
self
}
pub fn with_post_only(mut self, post_only: bool) -> Self {
self.post_only = Some(post_only);
self
}
pub fn with_client_id(mut self, id: impl Into<String>) -> Self {
self.cl_ord_id = Some(id.into());
self
}
pub fn with_validate(mut self, validate: bool) -> Self {
self.validate = Some(validate);
self
}
pub fn with_stp(mut self, stp: SelfTradePrevention) -> Self {
self.stp = Some(stp);
self
}
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
#[serde(rename_all = "lowercase")]
pub enum OrderStatus {
Pending,
Open,
Closed,
Canceled,
Expired,
Triggered,
}
#[derive(Debug, Clone, Deserialize)]
pub struct OrderResponse {
pub order_id: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub cl_ord_id: Option<String>,
pub order_status: OrderStatus,
pub timestamp: String,
}
#[derive(Debug, Clone, Serialize)]
pub struct AmendOrderParams {
pub order_id: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub order_qty: Option<f64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub limit_price: Option<f64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub trigger_price: Option<f64>,
}
#[derive(Debug, Clone, Deserialize)]
pub struct AmendOrderResponse {
pub order_id: String,
pub success: bool,
#[serde(skip_serializing_if = "Option::is_none")]
pub error: Option<String>,
}
#[derive(Debug, Clone, Deserialize)]
pub struct CancelOrderResponse {
pub order_id: String,
pub success: bool,
}
#[derive(Debug, Clone, Deserialize)]
pub struct CancelAllResponse {
pub count: usize,
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_market_buy_order() {
let order = OrderParams::market_buy("BTC/USD", 0.1);
assert_eq!(order.symbol, "BTC/USD");
assert_eq!(order.side, OrderSide::Buy);
assert_eq!(order.order_type, OrderType::Market);
assert_eq!(order.order_qty, Some(0.1));
}
#[test]
fn test_limit_sell_order() {
let order = OrderParams::limit_sell("ETH/USD", 1.0, 2500.0);
assert_eq!(order.symbol, "ETH/USD");
assert_eq!(order.side, OrderSide::Sell);
assert_eq!(order.order_type, OrderType::Limit);
assert_eq!(order.order_qty, Some(1.0));
assert_eq!(order.limit_price, Some(2500.0));
}
#[test]
fn test_order_builder() {
let order = OrderParams::limit_buy("BTC/USD", 0.5, 50000.0)
.with_time_in_force(TimeInForce::IOC)
.with_post_only(true)
.with_client_id("my-order-123");
assert_eq!(order.time_in_force, Some(TimeInForce::IOC));
assert_eq!(order.post_only, Some(true));
assert_eq!(order.cl_ord_id, Some("my-order-123".to_string()));
}
#[test]
fn test_validate_mode() {
let order = OrderParams::market_buy("BTC/USD", 0.1).with_validate(true);
assert_eq!(order.validate, Some(true));
}
}