use rust_decimal::Decimal;
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use crate::futures::types::*;
use crate::types::common::BuySell;
#[derive(Debug, Clone, Deserialize)]
pub struct TickersResponse {
pub result: String,
pub tickers: Vec<FuturesTicker>,
#[serde(rename = "serverTime")]
pub server_time: Option<String>,
}
#[derive(Debug, Clone, Deserialize)]
pub struct OrderBookResponse {
pub result: String,
#[serde(rename = "orderBook")]
pub order_book: FuturesOrderBook,
#[serde(rename = "serverTime")]
pub server_time: Option<String>,
}
#[derive(Debug, Clone, Deserialize)]
pub struct TradeHistoryResponse {
pub result: String,
pub history: Vec<FuturesTrade>,
#[serde(rename = "serverTime")]
pub server_time: Option<String>,
}
#[derive(Debug, Clone, Deserialize)]
pub struct InstrumentsResponse {
pub result: String,
pub instruments: Vec<FuturesInstrument>,
#[serde(rename = "serverTime")]
pub server_time: Option<String>,
}
#[derive(Debug, Clone, Deserialize)]
pub struct AccountsResponse {
pub result: String,
pub accounts: HashMap<String, FuturesAccount>,
#[serde(rename = "serverTime")]
pub server_time: Option<String>,
}
#[derive(Debug, Clone, Deserialize)]
pub struct OpenPositionsResponse {
pub result: String,
#[serde(rename = "openPositions")]
pub open_positions: Vec<FuturesPosition>,
#[serde(rename = "serverTime")]
pub server_time: Option<String>,
}
#[derive(Debug, Clone, Deserialize)]
pub struct OpenOrdersResponse {
pub result: String,
#[serde(alias = "openOrders", alias = "orders")]
pub open_orders: Vec<FuturesOrder>,
#[serde(rename = "serverTime")]
pub server_time: Option<String>,
}
#[derive(Debug, Clone, Serialize)]
pub struct FillsRequest {
#[serde(skip_serializing_if = "Option::is_none")]
pub symbol: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
#[serde(rename = "lastFillTime")]
pub last_fill_time: Option<String>,
}
#[derive(Debug, Clone, Deserialize)]
pub struct FillsResponse {
pub result: String,
pub fills: Vec<FuturesFill>,
#[serde(rename = "serverTime")]
pub server_time: Option<String>,
}
#[derive(Debug, Clone, Serialize)]
pub struct SendOrderRequest {
#[serde(rename = "orderType")]
pub order_type: FuturesOrderType,
pub symbol: String,
pub side: BuySell,
pub size: Decimal,
#[serde(skip_serializing_if = "Option::is_none")]
#[serde(rename = "limitPrice")]
pub limit_price: Option<Decimal>,
#[serde(skip_serializing_if = "Option::is_none")]
#[serde(rename = "stopPrice")]
pub stop_price: Option<Decimal>,
#[serde(skip_serializing_if = "Option::is_none")]
#[serde(rename = "triggerSignal")]
pub trigger_signal: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
#[serde(rename = "reduceOnly")]
pub reduce_only: Option<bool>,
#[serde(skip_serializing_if = "Option::is_none")]
#[serde(rename = "cliOrdId")]
pub cli_ord_id: Option<String>,
}
impl SendOrderRequest {
pub fn limit(symbol: impl Into<String>, side: BuySell, size: Decimal, price: Decimal) -> Self {
Self {
order_type: FuturesOrderType::Limit,
symbol: symbol.into(),
side,
size,
limit_price: Some(price),
stop_price: None,
trigger_signal: None,
reduce_only: None,
cli_ord_id: None,
}
}
pub fn market(symbol: impl Into<String>, side: BuySell, size: Decimal) -> Self {
Self {
order_type: FuturesOrderType::Market,
symbol: symbol.into(),
side,
size,
limit_price: None,
stop_price: None,
trigger_signal: None,
reduce_only: None,
cli_ord_id: None,
}
}
pub fn stop(
symbol: impl Into<String>,
side: BuySell,
size: Decimal,
stop_price: Decimal,
) -> Self {
Self {
order_type: FuturesOrderType::Stop,
symbol: symbol.into(),
side,
size,
limit_price: None,
stop_price: Some(stop_price),
trigger_signal: None,
reduce_only: None,
cli_ord_id: None,
}
}
pub fn reduce_only(mut self, reduce_only: bool) -> Self {
self.reduce_only = Some(reduce_only);
self
}
pub fn cli_ord_id(mut self, id: impl Into<String>) -> Self {
self.cli_ord_id = Some(id.into());
self
}
pub fn trigger_signal(mut self, signal: impl Into<String>) -> Self {
self.trigger_signal = Some(signal.into());
self
}
}
#[derive(Debug, Clone, Deserialize)]
pub struct SendOrderResponse {
pub result: String,
#[serde(rename = "sendStatus")]
pub send_status: SendStatus,
#[serde(rename = "serverTime")]
pub server_time: Option<String>,
}
#[derive(Debug, Clone, Deserialize)]
pub struct SendStatus {
#[serde(rename = "order_id")]
pub order_id: String,
pub status: String,
#[serde(rename = "receivedTime")]
pub received_time: Option<String>,
#[serde(rename = "cliOrdId")]
pub cli_ord_id: Option<String>,
}
#[derive(Debug, Clone, Serialize)]
pub struct EditOrderRequest {
#[serde(skip_serializing_if = "Option::is_none")]
#[serde(rename = "orderId")]
pub order_id: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
#[serde(rename = "cliOrdId")]
pub cli_ord_id: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub size: Option<Decimal>,
#[serde(skip_serializing_if = "Option::is_none")]
#[serde(rename = "limitPrice")]
pub limit_price: Option<Decimal>,
#[serde(skip_serializing_if = "Option::is_none")]
#[serde(rename = "stopPrice")]
pub stop_price: Option<Decimal>,
}
impl EditOrderRequest {
pub fn by_order_id(order_id: impl Into<String>) -> Self {
Self {
order_id: Some(order_id.into()),
cli_ord_id: None,
size: None,
limit_price: None,
stop_price: None,
}
}
pub fn by_cli_ord_id(cli_ord_id: impl Into<String>) -> Self {
Self {
order_id: None,
cli_ord_id: Some(cli_ord_id.into()),
size: None,
limit_price: None,
stop_price: None,
}
}
pub fn size(mut self, size: Decimal) -> Self {
self.size = Some(size);
self
}
pub fn limit_price(mut self, price: Decimal) -> Self {
self.limit_price = Some(price);
self
}
pub fn stop_price(mut self, price: Decimal) -> Self {
self.stop_price = Some(price);
self
}
}
#[derive(Debug, Clone, Deserialize)]
pub struct EditOrderResponse {
pub result: String,
#[serde(rename = "editStatus")]
pub edit_status: EditStatus,
#[serde(rename = "serverTime")]
pub server_time: Option<String>,
}
#[derive(Debug, Clone, Deserialize)]
pub struct EditStatus {
#[serde(rename = "orderId")]
pub order_id: String,
pub status: String,
#[serde(rename = "receivedTime")]
pub received_time: Option<String>,
}
#[derive(Debug, Clone, Deserialize)]
pub struct CancelOrderResponse {
pub result: String,
#[serde(rename = "cancelStatus")]
pub cancel_status: CancelStatus,
#[serde(rename = "serverTime")]
pub server_time: Option<String>,
}
#[derive(Debug, Clone, Deserialize)]
pub struct CancelStatus {
#[serde(rename = "order_id")]
pub order_id: Option<String>,
#[serde(rename = "cliOrdId")]
pub cli_ord_id: Option<String>,
pub status: String,
#[serde(rename = "receivedTime")]
pub received_time: Option<String>,
}
#[derive(Debug, Clone, Deserialize)]
pub struct CancelAllOrdersResponse {
pub result: String,
#[serde(rename = "cancelStatus")]
pub cancel_status: CancelAllStatus,
#[serde(rename = "serverTime")]
pub server_time: Option<String>,
}
#[derive(Debug, Clone, Deserialize)]
pub struct CancelAllStatus {
#[serde(rename = "cancelledOrders")]
pub cancelled_orders: Option<Vec<CancelledOrder>>,
pub status: Option<String>,
#[serde(rename = "receivedTime")]
pub received_time: Option<String>,
}
#[derive(Debug, Clone, Deserialize)]
pub struct CancelledOrder {
#[serde(rename = "order_id")]
pub order_id: String,
}
#[derive(Debug, Clone, Deserialize)]
pub struct CancelAllOrdersAfterResponse {
pub result: String,
pub status: String,
#[serde(rename = "currentTime")]
pub current_time: Option<String>,
#[serde(rename = "triggerTime")]
pub trigger_time: Option<String>,
#[serde(rename = "serverTime")]
pub server_time: Option<String>,
}
#[derive(Debug, Clone, Serialize)]
pub struct BatchOrderRequest {
#[serde(rename = "batchOrder")]
pub batch_order: Vec<BatchElement>,
}
impl BatchOrderRequest {
pub fn new() -> Self {
Self {
batch_order: Vec::new(),
}
}
pub fn place(mut self, order: SendOrderRequest) -> Self {
self.batch_order.push(BatchElement::Place(PlaceBatchElement {
order_type: order.order_type,
symbol: order.symbol,
side: order.side,
size: order.size,
limit_price: order.limit_price,
stop_price: order.stop_price,
reduce_only: order.reduce_only,
cli_ord_id: order.cli_ord_id,
}));
self
}
pub fn cancel(mut self, order_id: impl Into<String>) -> Self {
self.batch_order.push(BatchElement::Cancel(CancelBatchElement {
order_id: Some(order_id.into()),
cli_ord_id: None,
}));
self
}
pub fn cancel_by_cli_ord_id(mut self, cli_ord_id: impl Into<String>) -> Self {
self.batch_order.push(BatchElement::Cancel(CancelBatchElement {
order_id: None,
cli_ord_id: Some(cli_ord_id.into()),
}));
self
}
}
impl Default for BatchOrderRequest {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug, Clone, Serialize)]
#[serde(tag = "order", rename_all = "lowercase")]
pub enum BatchElement {
Place(PlaceBatchElement),
Cancel(CancelBatchElement),
}
#[derive(Debug, Clone, Serialize)]
pub struct PlaceBatchElement {
#[serde(rename = "orderType")]
pub order_type: FuturesOrderType,
pub symbol: String,
pub side: BuySell,
pub size: Decimal,
#[serde(skip_serializing_if = "Option::is_none")]
#[serde(rename = "limitPrice")]
pub limit_price: Option<Decimal>,
#[serde(skip_serializing_if = "Option::is_none")]
#[serde(rename = "stopPrice")]
pub stop_price: Option<Decimal>,
#[serde(skip_serializing_if = "Option::is_none")]
#[serde(rename = "reduceOnly")]
pub reduce_only: Option<bool>,
#[serde(skip_serializing_if = "Option::is_none")]
#[serde(rename = "cliOrdId")]
pub cli_ord_id: Option<String>,
}
#[derive(Debug, Clone, Serialize)]
pub struct CancelBatchElement {
#[serde(skip_serializing_if = "Option::is_none")]
#[serde(rename = "order_id")]
pub order_id: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
#[serde(rename = "cliOrdId")]
pub cli_ord_id: Option<String>,
}
#[derive(Debug, Clone, Deserialize)]
pub struct BatchOrderResponse {
pub result: String,
#[serde(rename = "batchStatus")]
pub batch_status: Vec<BatchElementStatus>,
#[serde(rename = "serverTime")]
pub server_time: Option<String>,
}
#[derive(Debug, Clone, Deserialize)]
pub struct BatchElementStatus {
#[serde(rename = "order_id")]
pub order_id: Option<String>,
pub status: String,
#[serde(rename = "errorMessage")]
pub error_message: Option<String>,
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_send_order_request_limit() {
let request = SendOrderRequest::limit("PI_XBTUSD", BuySell::Buy, Decimal::from(100), Decimal::from(50000))
.reduce_only(true)
.cli_ord_id("my-order-1");
let json = serde_json::to_string(&request).unwrap();
assert!(json.contains("limitPrice"));
assert!(json.contains("reduceOnly"));
assert!(json.contains("cliOrdId"));
}
#[test]
fn test_send_order_request_market() {
let request = SendOrderRequest::market("PI_ETHUSD", BuySell::Sell, Decimal::from(50));
let json = serde_json::to_string(&request).unwrap();
assert!(!json.contains("limitPrice"));
assert!(json.contains("PI_ETHUSD"));
}
#[test]
fn test_edit_order_request() {
let request = EditOrderRequest::by_order_id("abc123")
.size(Decimal::from(200))
.limit_price(Decimal::from(51000));
let json = serde_json::to_string(&request).unwrap();
assert!(json.contains("orderId"));
assert!(json.contains("size"));
assert!(json.contains("limitPrice"));
}
#[test]
fn test_batch_order_request() {
let batch = BatchOrderRequest::new()
.place(SendOrderRequest::limit("PI_XBTUSD", BuySell::Buy, Decimal::from(100), Decimal::from(50000)))
.cancel("order-to-cancel");
assert_eq!(batch.batch_order.len(), 2);
}
#[test]
fn test_deserialize_send_order_response() {
let json = r#"{
"result": "success",
"sendStatus": {
"order_id": "abc123",
"status": "placed",
"receivedTime": "2024-01-15T10:00:00Z"
},
"serverTime": "2024-01-15T10:00:00Z"
}"#;
let response: SendOrderResponse = serde_json::from_str(json).unwrap();
assert_eq!(response.result, "success");
assert_eq!(response.send_status.order_id, "abc123");
}
}