//! Perpetuals Client use crate::dex::types::*; use reqwest::Client; use serde_json::json; use std::sync::atomic::AtomicBool; use std::sync::Arc; /// Perpetuals sub-client pub struct PerpsClient { config: DexConfig, client: Client, closed: Arc, } impl PerpsClient { pub(crate) fn new(config: DexConfig, client: Client, closed: Arc) -> Self { Self { config, client, closed } } /// List all perpetual markets pub async fn list_markets(&self) -> Result, DexError> { self.get("/perps/markets").await } /// Get a specific perpetual market pub async fn get_market(&self, symbol: &str) -> Result { self.get(&format!("/perps/markets/{}", symbol)).await } /// Open a perpetual position pub async fn open_position(&self, params: OpenPositionParams) -> Result { let mut body = json!({ "market": params.market, "side": params.side, "size": params.size, "leverage": params.leverage, "order_type": params.order_type, "margin_type": params.margin_type, "reduce_only": params.reduce_only, }); if let Some(price) = params.limit_price { body["limit_price"] = json!(price); } if let Some(sl) = params.stop_loss { body["stop_loss"] = json!(sl); } if let Some(tp) = params.take_profit { body["take_profit"] = json!(tp); } self.post("/perps/positions", body).await } /// Close a perpetual position pub async fn close_position(&self, params: ClosePositionParams) -> Result { let mut body = json!({ "market": params.market, "order_type": params.order_type, }); if let Some(size) = params.size { body["size"] = json!(size); } if let Some(price) = params.limit_price { body["limit_price"] = json!(price); } self.post("/perps/positions/close", body).await } /// Modify a perpetual position pub async fn modify_position(&self, params: ModifyPositionParams) -> Result { let mut body = json!({}); if let Some(leverage) = params.new_leverage { body["new_leverage"] = json!(leverage); } if let Some(margin) = params.new_margin { body["new_margin"] = json!(margin); } if let Some(sl) = params.new_stop_loss { body["new_stop_loss"] = json!(sl); } if let Some(tp) = params.new_take_profit { body["new_take_profit"] = json!(tp); } self.post(&format!("/perps/positions/{}/modify", params.position_id), body).await } /// Get all open positions pub async fn get_positions(&self) -> Result, DexError> { self.get("/perps/positions").await } /// Get position for a specific market pub async fn get_position(&self, market: &str) -> Result, DexError> { self.get(&format!("/perps/positions/{}", market)).await } /// Get all open orders pub async fn get_orders(&self) -> Result, DexError> { self.get("/perps/orders").await } /// Cancel an order pub async fn cancel_order(&self, order_id: &str) -> Result<(), DexError> { self.delete(&format!("/perps/orders/{}", order_id)).await } /// Cancel all orders pub async fn cancel_all_orders(&self, market: Option<&str>) -> Result { let path = match market { Some(m) => format!("/perps/orders?market={}", m), None => "/perps/orders".to_string(), }; #[derive(serde::Deserialize)] struct CancelResult { cancelled: u32, } let result: CancelResult = self.delete(&path).await?; Ok(result.cancelled) } /// Get funding payment history pub async fn get_funding_history(&self, market: &str, limit: u32) -> Result, DexError> { self.get(&format!("/perps/funding/{}?limit={}", market, limit)).await } /// Get current funding rate pub async fn get_funding_rate(&self, market: &str) -> Result { self.get(&format!("/perps/funding/{}/current", market)).await } // Internal methods async fn get(&self, path: &str) -> Result { use std::sync::atomic::Ordering; if self.closed.load(Ordering::SeqCst) { return Err(DexError::ClientClosed); } let url = format!("{}{}", self.config.endpoint, path); let response = self.client .get(&url) .header("Content-Type", "application/json") .header("Authorization", format!("Bearer {}", self.config.api_key)) .header("X-SDK-Version", "rust/0.1.0") .send() .await?; if !response.status().is_success() { return Err(DexError::Http { message: format!("HTTP {}", response.status()), code: None, status: Some(response.status().as_u16()), }); } response.json().await.map_err(DexError::from) } async fn post(&self, path: &str, body: serde_json::Value) -> Result { use std::sync::atomic::Ordering; if self.closed.load(Ordering::SeqCst) { return Err(DexError::ClientClosed); } let url = format!("{}{}", self.config.endpoint, path); let response = self.client .post(&url) .header("Content-Type", "application/json") .header("Authorization", format!("Bearer {}", self.config.api_key)) .header("X-SDK-Version", "rust/0.1.0") .json(&body) .send() .await?; if !response.status().is_success() { return Err(DexError::Http { message: format!("HTTP {}", response.status()), code: None, status: Some(response.status().as_u16()), }); } response.json().await.map_err(DexError::from) } async fn delete(&self, path: &str) -> Result { use std::sync::atomic::Ordering; if self.closed.load(Ordering::SeqCst) { return Err(DexError::ClientClosed); } let url = format!("{}{}", self.config.endpoint, path); let response = self.client .delete(&url) .header("Content-Type", "application/json") .header("Authorization", format!("Bearer {}", self.config.api_key)) .header("X-SDK-Version", "rust/0.1.0") .send() .await?; if !response.status().is_success() { return Err(DexError::Http { message: format!("HTTP {}", response.status()), code: None, status: Some(response.status().as_u16()), }); } response.json().await.map_err(DexError::from) } }