use serde::{Deserialize, Serialize};
use crate::types::{
AccountType, CancelReplaceResult, ContingencyType, OcoOrderStatus, OcoStatus, OrderSide,
OrderStatus, OrderType, TimeInForce,
};
use super::market::string_or_float;
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct AccountInfo {
pub maker_commission: i32,
pub taker_commission: i32,
pub buyer_commission: i32,
pub seller_commission: i32,
#[serde(default)]
pub commission_rates: Option<CommissionRates>,
pub can_trade: bool,
pub can_withdraw: bool,
pub can_deposit: bool,
#[serde(default)]
pub brokered: bool,
#[serde(default)]
pub require_self_trade_prevention: bool,
pub update_time: u64,
pub account_type: AccountType,
pub balances: Vec<Balance>,
#[serde(default)]
pub permissions: Vec<AccountType>,
#[serde(default)]
pub uid: Option<u64>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct CommissionRates {
#[serde(with = "string_or_float")]
pub maker: f64,
#[serde(with = "string_or_float")]
pub taker: f64,
#[serde(with = "string_or_float")]
pub buyer: f64,
#[serde(with = "string_or_float")]
pub seller: f64,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct CommissionRateDetail {
#[serde(with = "string_or_float")]
pub maker: f64,
#[serde(with = "string_or_float")]
pub taker: f64,
#[serde(with = "string_or_float")]
pub buyer: f64,
#[serde(with = "string_or_float")]
pub seller: f64,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct CommissionDiscount {
pub enabled_for_account: bool,
pub enabled_for_symbol: bool,
pub discount_asset: String,
#[serde(with = "string_or_float")]
pub discount: f64,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct AccountCommission {
pub symbol: String,
pub standard_commission: CommissionRateDetail,
pub special_commission: CommissionRateDetail,
pub tax_commission: CommissionRateDetail,
pub discount: CommissionDiscount,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct OrderCommissionRate {
#[serde(with = "string_or_float")]
pub maker: f64,
#[serde(with = "string_or_float")]
pub taker: f64,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct SorOrderCommissionRates {
pub standard_commission_for_order: OrderCommissionRate,
pub tax_commission_for_order: OrderCommissionRate,
pub discount: CommissionDiscount,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(untagged)]
pub enum SorOrderTestResponse {
Empty(EmptyResponse),
Rates(SorOrderCommissionRates),
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct PreventedMatch {
pub symbol: String,
pub prevented_match_id: u64,
pub taker_order_id: u64,
pub maker_symbol: String,
pub maker_order_id: u64,
pub trade_group_id: u64,
pub self_trade_prevention_mode: String,
#[serde(with = "string_or_float")]
pub price: f64,
#[serde(with = "string_or_float")]
pub maker_prevented_quantity: f64,
pub transact_time: u64,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct Allocation {
pub symbol: String,
pub allocation_id: u64,
pub allocation_type: String,
pub order_id: u64,
pub order_list_id: i64,
#[serde(with = "string_or_float")]
pub price: f64,
#[serde(with = "string_or_float")]
pub qty: f64,
#[serde(with = "string_or_float")]
pub quote_qty: f64,
#[serde(with = "string_or_float")]
pub commission: f64,
pub commission_asset: String,
pub time: u64,
pub is_buyer: bool,
pub is_maker: bool,
pub is_allocator: bool,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct Balance {
pub asset: String,
#[serde(with = "string_or_float")]
pub free: f64,
#[serde(with = "string_or_float")]
pub locked: f64,
}
impl Balance {
pub fn total(&self) -> f64 {
self.free + self.locked
}
pub fn is_zero(&self) -> bool {
self.free == 0.0 && self.locked == 0.0
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct Order {
pub symbol: String,
pub order_id: u64,
pub order_list_id: i64,
pub client_order_id: String,
#[serde(with = "string_or_float")]
pub price: f64,
#[serde(with = "string_or_float")]
pub orig_qty: f64,
#[serde(with = "string_or_float")]
pub executed_qty: f64,
#[serde(with = "string_or_float")]
pub cummulative_quote_qty: f64,
pub status: OrderStatus,
pub time_in_force: TimeInForce,
#[serde(rename = "type")]
pub order_type: OrderType,
pub side: OrderSide,
#[serde(with = "string_or_float")]
pub stop_price: f64,
#[serde(with = "string_or_float")]
pub iceberg_qty: f64,
pub time: u64,
pub update_time: u64,
pub is_working: bool,
#[serde(with = "string_or_float")]
pub orig_quote_order_qty: f64,
#[serde(default)]
pub working_time: Option<u64>,
#[serde(default)]
pub self_trade_prevention_mode: Option<String>,
}
impl Order {
pub fn avg_price(&self) -> Option<f64> {
if self.executed_qty > 0.0 {
Some(self.cummulative_quote_qty / self.executed_qty)
} else {
None
}
}
pub fn is_filled(&self) -> bool {
self.status == OrderStatus::Filled
}
pub fn is_active(&self) -> bool {
matches!(self.status, OrderStatus::New | OrderStatus::PartiallyFilled)
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct OrderAck {
pub symbol: String,
pub order_id: u64,
#[serde(default)]
pub order_list_id: i64,
pub client_order_id: String,
pub transact_time: u64,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct OrderResult {
pub symbol: String,
pub order_id: u64,
#[serde(default)]
pub order_list_id: i64,
pub client_order_id: String,
pub transact_time: u64,
#[serde(with = "string_or_float")]
pub price: f64,
#[serde(with = "string_or_float")]
pub orig_qty: f64,
#[serde(with = "string_or_float")]
pub executed_qty: f64,
#[serde(with = "string_or_float")]
pub cummulative_quote_qty: f64,
pub status: OrderStatus,
pub time_in_force: TimeInForce,
#[serde(rename = "type")]
pub order_type: OrderType,
pub side: OrderSide,
#[serde(default)]
pub working_time: Option<u64>,
#[serde(default)]
pub self_trade_prevention_mode: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct OrderFull {
pub symbol: String,
pub order_id: u64,
#[serde(default)]
pub order_list_id: i64,
pub client_order_id: String,
pub transact_time: u64,
#[serde(with = "string_or_float")]
pub price: f64,
#[serde(with = "string_or_float")]
pub orig_qty: f64,
#[serde(with = "string_or_float")]
pub executed_qty: f64,
#[serde(with = "string_or_float")]
pub cummulative_quote_qty: f64,
pub status: OrderStatus,
pub time_in_force: TimeInForce,
#[serde(rename = "type")]
pub order_type: OrderType,
pub side: OrderSide,
#[serde(default)]
pub working_time: Option<u64>,
#[serde(default)]
pub self_trade_prevention_mode: Option<String>,
#[serde(default)]
pub fills: Vec<Fill>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct Fill {
#[serde(with = "string_or_float")]
pub price: f64,
#[serde(rename = "qty", with = "string_or_float")]
pub quantity: f64,
#[serde(with = "string_or_float")]
pub commission: f64,
pub commission_asset: String,
#[serde(default)]
pub trade_id: Option<u64>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct CancelOrderResponse {
pub symbol: String,
pub orig_client_order_id: String,
pub order_id: u64,
#[serde(default)]
pub order_list_id: i64,
pub client_order_id: String,
#[serde(with = "string_or_float")]
pub price: f64,
#[serde(with = "string_or_float")]
pub orig_qty: f64,
#[serde(with = "string_or_float")]
pub executed_qty: f64,
#[serde(with = "string_or_float")]
pub cummulative_quote_qty: f64,
pub status: OrderStatus,
pub time_in_force: TimeInForce,
#[serde(rename = "type")]
pub order_type: OrderType,
pub side: OrderSide,
#[serde(default)]
pub self_trade_prevention_mode: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct CancelReplaceErrorInfo {
pub code: i32,
pub msg: String,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct CancelReplaceErrorData {
pub cancel_result: CancelReplaceResult,
pub new_order_result: CancelReplaceResult,
pub cancel_response: CancelReplaceSideResponse,
pub new_order_response: Option<CancelReplaceSideResponse>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct CancelReplaceErrorResponse {
pub code: i32,
pub msg: String,
pub data: CancelReplaceErrorData,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(untagged)]
pub enum CancelReplaceSideResponse {
Error(CancelReplaceErrorInfo),
Cancel(CancelOrderResponse),
Order(OrderResponse),
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct CancelReplaceResponse {
pub cancel_result: CancelReplaceResult,
pub new_order_result: CancelReplaceResult,
pub cancel_response: CancelOrderResponse,
pub new_order_response: OrderResponse,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(untagged)]
pub enum OrderResponse {
Ack(OrderAck),
Result(OrderResult),
Full(OrderFull),
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct UserTrade {
pub symbol: String,
pub id: u64,
pub order_id: u64,
#[serde(default)]
pub order_list_id: i64,
#[serde(with = "string_or_float")]
pub price: f64,
#[serde(rename = "qty", with = "string_or_float")]
pub quantity: f64,
#[serde(rename = "quoteQty", with = "string_or_float")]
pub quote_quantity: f64,
#[serde(with = "string_or_float")]
pub commission: f64,
pub commission_asset: String,
pub time: u64,
pub is_buyer: bool,
pub is_maker: bool,
pub is_best_match: bool,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct OcoOrder {
pub order_list_id: u64,
pub contingency_type: ContingencyType,
pub list_status_type: OcoStatus,
pub list_order_status: OcoOrderStatus,
pub list_client_order_id: String,
pub transaction_time: u64,
pub symbol: String,
pub orders: Vec<OcoOrderDetail>,
#[serde(default)]
pub order_reports: Vec<OcoOrderReport>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct OcoOrderDetail {
pub symbol: String,
pub order_id: u64,
pub client_order_id: String,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct OcoOrderReport {
pub symbol: String,
pub order_id: u64,
pub order_list_id: i64,
pub client_order_id: String,
pub transact_time: u64,
#[serde(with = "string_or_float")]
pub price: f64,
#[serde(with = "string_or_float")]
pub orig_qty: f64,
#[serde(with = "string_or_float")]
pub executed_qty: f64,
#[serde(with = "string_or_float")]
pub cummulative_quote_qty: f64,
pub status: OrderStatus,
pub time_in_force: TimeInForce,
#[serde(rename = "type")]
pub order_type: OrderType,
pub side: OrderSide,
#[serde(default, with = "super::market::string_or_float_opt")]
pub stop_price: Option<f64>,
#[serde(default)]
pub self_trade_prevention_mode: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct ListenKey {
pub listen_key: String,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct EmptyResponse {}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct UnfilledOrderCount {
pub rate_limit_type: String,
pub interval: String,
pub interval_num: u32,
pub limit: u32,
pub count: u32,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct OrderAmendment {
pub symbol: String,
pub order_id: u64,
pub execution_id: u64,
pub orig_client_order_id: String,
pub new_client_order_id: String,
#[serde(with = "string_or_float")]
pub orig_qty: f64,
#[serde(with = "string_or_float")]
pub new_qty: f64,
pub time: u64,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct AmendedOrderInfo {
pub symbol: String,
pub order_id: u64,
pub order_list_id: i64,
pub orig_client_order_id: String,
pub client_order_id: String,
#[serde(with = "string_or_float")]
pub price: f64,
#[serde(rename = "qty", with = "string_or_float")]
pub quantity: f64,
#[serde(with = "string_or_float")]
pub executed_qty: f64,
#[serde(with = "string_or_float")]
pub prevented_qty: f64,
#[serde(with = "string_or_float")]
pub quote_order_qty: f64,
#[serde(with = "string_or_float")]
pub cumulative_quote_qty: f64,
pub status: OrderStatus,
pub time_in_force: TimeInForce,
#[serde(rename = "type")]
pub order_type: OrderType,
pub side: OrderSide,
#[serde(default)]
pub working_time: Option<u64>,
#[serde(default)]
pub self_trade_prevention_mode: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct AmendListStatus {
pub order_list_id: u64,
pub contingency_type: ContingencyType,
pub list_order_status: OcoOrderStatus,
pub list_client_order_id: String,
pub symbol: String,
pub orders: Vec<OcoOrderDetail>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct AmendOrderResponse {
pub transact_time: u64,
pub execution_id: u64,
pub amended_order: AmendedOrderInfo,
#[serde(default)]
pub list_status: Option<AmendListStatus>,
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_balance_deserialize() {
let json = r#"{
"asset": "BTC",
"free": "1.5",
"locked": "0.5"
}"#;
let balance: Balance = serde_json::from_str(json).unwrap();
assert_eq!(balance.asset, "BTC");
assert_eq!(balance.free, 1.5);
assert_eq!(balance.locked, 0.5);
assert_eq!(balance.total(), 2.0);
assert!(!balance.is_zero());
}
#[test]
fn test_balance_zero() {
let balance = Balance {
asset: "BTC".to_string(),
free: 0.0,
locked: 0.0,
};
assert!(balance.is_zero());
}
#[test]
fn test_order_deserialize() {
let json = r#"{
"symbol": "BTCUSDT",
"orderId": 12345,
"orderListId": -1,
"clientOrderId": "test123",
"price": "50000.00",
"origQty": "1.0",
"executedQty": "0.5",
"cummulativeQuoteQty": "25000.00",
"status": "PARTIALLY_FILLED",
"timeInForce": "GTC",
"type": "LIMIT",
"side": "BUY",
"stopPrice": "0.0",
"icebergQty": "0.0",
"time": 1234567890123,
"updateTime": 1234567890123,
"isWorking": true,
"origQuoteOrderQty": "0.0"
}"#;
let order: Order = serde_json::from_str(json).unwrap();
assert_eq!(order.symbol, "BTCUSDT");
assert_eq!(order.order_id, 12345);
assert_eq!(order.price, 50000.0);
assert_eq!(order.status, OrderStatus::PartiallyFilled);
assert!(order.is_active());
assert!(!order.is_filled());
assert_eq!(order.avg_price(), Some(50000.0));
}
#[test]
fn test_order_full_deserialize() {
let json = r#"{
"symbol": "BTCUSDT",
"orderId": 12345,
"orderListId": -1,
"clientOrderId": "test123",
"transactTime": 1234567890123,
"price": "50000.00",
"origQty": "1.0",
"executedQty": "1.0",
"cummulativeQuoteQty": "50000.00",
"status": "FILLED",
"timeInForce": "GTC",
"type": "LIMIT",
"side": "BUY",
"fills": [
{
"price": "50000.00",
"qty": "1.0",
"commission": "0.001",
"commissionAsset": "BTC"
}
]
}"#;
let order: OrderFull = serde_json::from_str(json).unwrap();
assert_eq!(order.symbol, "BTCUSDT");
assert_eq!(order.status, OrderStatus::Filled);
assert_eq!(order.fills.len(), 1);
assert_eq!(order.fills[0].price, 50000.0);
assert_eq!(order.fills[0].commission, 0.001);
}
#[test]
fn test_user_trade_deserialize() {
let json = r#"{
"symbol": "BTCUSDT",
"id": 12345,
"orderId": 67890,
"price": "50000.00",
"qty": "1.0",
"quoteQty": "50000.00",
"commission": "0.001",
"commissionAsset": "BTC",
"time": 1234567890123,
"isBuyer": true,
"isMaker": false,
"isBestMatch": true
}"#;
let trade: UserTrade = serde_json::from_str(json).unwrap();
assert_eq!(trade.symbol, "BTCUSDT");
assert_eq!(trade.id, 12345);
assert_eq!(trade.price, 50000.0);
assert!(trade.is_buyer);
assert!(!trade.is_maker);
}
#[test]
fn test_listen_key_deserialize() {
let json = r#"{"listenKey": "abc123xyz"}"#;
let key: ListenKey = serde_json::from_str(json).unwrap();
assert_eq!(key.listen_key, "abc123xyz");
}
#[test]
fn test_account_info_deserialize() {
let json = r#"{
"makerCommission": 10,
"takerCommission": 10,
"buyerCommission": 0,
"sellerCommission": 0,
"canTrade": true,
"canWithdraw": true,
"canDeposit": true,
"updateTime": 1234567890123,
"accountType": "SPOT",
"balances": [
{"asset": "BTC", "free": "1.0", "locked": "0.0"}
],
"permissions": ["SPOT"]
}"#;
let account: AccountInfo = serde_json::from_str(json).unwrap();
assert_eq!(account.maker_commission, 10);
assert!(account.can_trade);
assert_eq!(account.account_type, AccountType::Spot);
assert_eq!(account.balances.len(), 1);
assert_eq!(account.balances[0].asset, "BTC");
}
}