deribit_http/endpoints/
private.rs

1//! Private endpoints for authenticated API calls
2
3use crate::DeribitHttpClient;
4use crate::constants::endpoints::*;
5use crate::error::HttpError;
6use crate::model::account::Subaccount;
7use crate::model::position::Position;
8use crate::model::request::mass_quote::MassQuoteRequest;
9use crate::model::request::order::OrderRequest;
10use crate::model::request::trade::TradesRequest;
11use crate::model::response::api_response::ApiResponse;
12use crate::model::response::deposit::DepositsResponse;
13use crate::model::response::mass_quote::MassQuoteResponse;
14use crate::model::response::order::{OrderInfoResponse, OrderResponse};
15use crate::model::response::other::{
16    AccountSummaryResponse, TransactionLogResponse, TransferResultResponse,
17};
18use crate::model::response::withdrawal::WithdrawalsResponse;
19use crate::model::{
20    TransactionLogRequest, UserTradeResponseByOrder, UserTradeWithPaginationResponse,
21};
22use crate::prelude::Trigger;
23
24/// Private endpoints implementation
25impl DeribitHttpClient {
26    /// Get subaccounts
27    ///
28    /// Retrieves the list of subaccounts associated with the main account.
29    ///
30    /// # Arguments
31    ///
32    /// * `with_portfolio` - Include portfolio information (optional)
33    ///
34    /// # Examples
35    ///
36    /// ```rust
37    /// use deribit_http::DeribitHttpClient;
38    ///
39    /// let client = DeribitHttpClient::new();
40    /// // let subaccounts = client.get_subaccounts(Some(true)).await?;
41    /// // tracing::info!("Found {} subaccounts", subaccounts.len());
42    /// ```
43    pub async fn get_subaccounts(
44        &self,
45        with_portfolio: Option<bool>,
46    ) -> Result<Vec<Subaccount>, HttpError> {
47        let mut query_params = Vec::new();
48
49        if let Some(with_portfolio) = with_portfolio {
50            query_params.push(("with_portfolio".to_string(), with_portfolio.to_string()));
51        }
52
53        let query_string = if query_params.is_empty() {
54            String::new()
55        } else {
56            "?".to_string()
57                + &query_params
58                    .iter()
59                    .map(|(k, v)| format!("{}={}", k, urlencoding::encode(v)))
60                    .collect::<Vec<_>>()
61                    .join("&")
62        };
63
64        let url = format!("{}{}{}", self.base_url(), GET_SUBACCOUNTS, query_string);
65
66        let response = self.make_authenticated_request(&url).await?;
67
68        if !response.status().is_success() {
69            let error_text = response
70                .text()
71                .await
72                .unwrap_or_else(|_| "Unknown error".to_string());
73            return Err(HttpError::RequestFailed(format!(
74                "Get subaccounts failed: {}",
75                error_text
76            )));
77        }
78
79        // Debug: Get raw response text first
80        let response_text = response.text().await.map_err(|e| {
81            HttpError::InvalidResponse(format!("Failed to read response text: {}", e))
82        })?;
83
84        tracing::debug!("Raw API response: {}", response_text);
85
86        let api_response: ApiResponse<Vec<Subaccount>> = serde_json::from_str(&response_text)
87            .map_err(|e| {
88                HttpError::InvalidResponse(format!(
89                    "Failed to parse JSON: {} - Raw response: {}",
90                    e, response_text
91                ))
92            })?;
93
94        if let Some(error) = api_response.error {
95            return Err(HttpError::RequestFailed(format!(
96                "API error: {} - {}",
97                error.code, error.message
98            )));
99        }
100
101        api_response.result.ok_or_else(|| {
102            HttpError::InvalidResponse("No subaccounts data in response".to_string())
103        })
104    }
105
106    /// Get transaction log
107    ///
108    /// Retrieves transaction log entries for the account.
109    ///
110    /// # Arguments
111    ///
112    /// * `request` - A `TransactionLogRequest` struct containing:
113    ///   * `currency` - Currency symbol (BTC, ETH, etc.)
114    ///   * `start_timestamp` - Start timestamp in milliseconds (optional)
115    ///   * `end_timestamp` - End timestamp in milliseconds (optional)
116    ///   * `count` - Number of requested items (optional, default 10)
117    ///   * `continuation` - Continuation token for pagination (optional)
118    ///
119    /// # Examples
120    ///
121    /// ```rust
122    /// use deribit_http::DeribitHttpClient;
123    /// use crate::model::TransactionLogRequest;
124    ///
125    /// let client = DeribitHttpClient::new();
126    /// // let request = TransactionLogRequest { currency: "BTC".into(), ..Default::default() };
127    /// // let log = client.get_transaction_log(request).await?;
128    /// ```
129    pub async fn get_transaction_log(
130        &self,
131        request: TransactionLogRequest,
132    ) -> Result<TransactionLogResponse, HttpError> {
133        let mut query_params = vec![("currency".to_string(), request.currency.to_string())];
134
135        query_params.push((
136            "start_timestamp".to_string(),
137            request.start_timestamp.to_string(),
138        ));
139        query_params.push((
140            "end_timestamp".to_string(),
141            request.end_timestamp.to_string(),
142        ));
143
144        if let Some(query) = request.query {
145            query_params.push(("query".to_string(), query));
146        }
147
148        if let Some(count) = request.count {
149            query_params.push(("count".to_string(), count.to_string()));
150        }
151
152        if let Some(subaccount_id) = request.subaccount_id {
153            query_params.push(("subaccount_id".to_string(), subaccount_id.to_string()));
154        }
155
156        if let Some(continuation) = request.continuation {
157            query_params.push(("continuation".to_string(), continuation.to_string()));
158        }
159
160        let query_string = query_params
161            .iter()
162            .map(|(k, v)| format!("{}={}", k, urlencoding::encode(v)))
163            .collect::<Vec<_>>()
164            .join("&");
165
166        let url = format!(
167            "{}{}?{}",
168            self.base_url(),
169            GET_TRANSACTION_LOG,
170            query_string
171        );
172
173        let response = self.make_authenticated_request(&url).await?;
174
175        if !response.status().is_success() {
176            let error_text = response
177                .text()
178                .await
179                .unwrap_or_else(|_| "Unknown error".to_string());
180            return Err(HttpError::RequestFailed(format!(
181                "Get transaction log failed: {}",
182                error_text
183            )));
184        }
185
186        let api_response: ApiResponse<TransactionLogResponse> = response
187            .json()
188            .await
189            .map_err(|e| HttpError::InvalidResponse(e.to_string()))?;
190
191        if let Some(error) = api_response.error {
192            return Err(HttpError::RequestFailed(format!(
193                "API error: {} - {}",
194                error.code, error.message
195            )));
196        }
197
198        api_response.result.ok_or_else(|| {
199            HttpError::InvalidResponse("No transaction log data in response".to_string())
200        })
201    }
202
203    /// Get deposits
204    ///
205    /// Retrieves the latest user deposits.
206    ///
207    /// # Arguments
208    ///
209    /// * `currency` - Currency symbol (BTC, ETH, etc.)
210    /// * `count` - Number of requested items (optional, default 10)
211    /// * `offset` - Offset for pagination (optional, default 0)
212    ///
213    /// # Examples
214    ///
215    /// ```rust
216    /// use deribit_http::DeribitHttpClient;
217    ///
218    /// let client = DeribitHttpClient::new();
219    /// // let deposits = client.get_deposits("BTC", Some(20), Some(0)).await?;
220    /// // tracing::info!("Found {} deposits", deposits.data.len());
221    /// ```
222    pub async fn get_deposits(
223        &self,
224        currency: &str,
225        count: Option<u32>,
226        offset: Option<u32>,
227    ) -> Result<DepositsResponse, HttpError> {
228        let mut query_params = vec![("currency".to_string(), currency.to_string())];
229
230        if let Some(count) = count {
231            query_params.push(("count".to_string(), count.to_string()));
232        }
233
234        if let Some(offset) = offset {
235            query_params.push(("offset".to_string(), offset.to_string()));
236        }
237
238        let query_string = query_params
239            .iter()
240            .map(|(k, v)| format!("{}={}", k, urlencoding::encode(v)))
241            .collect::<Vec<_>>()
242            .join("&");
243
244        let url = format!("{}{}?{}", self.base_url(), GET_DEPOSITS, query_string);
245
246        let response = self.make_authenticated_request(&url).await?;
247
248        if !response.status().is_success() {
249            let error_text = response
250                .text()
251                .await
252                .unwrap_or_else(|_| "Unknown error".to_string());
253            return Err(HttpError::RequestFailed(format!(
254                "Get deposits failed: {}",
255                error_text
256            )));
257        }
258
259        let api_response: ApiResponse<DepositsResponse> = response
260            .json()
261            .await
262            .map_err(|e| HttpError::InvalidResponse(e.to_string()))?;
263
264        if let Some(error) = api_response.error {
265            return Err(HttpError::RequestFailed(format!(
266                "API error: {} - {}",
267                error.code, error.message
268            )));
269        }
270
271        api_response
272            .result
273            .ok_or_else(|| HttpError::InvalidResponse("No deposits data in response".to_string()))
274    }
275
276    /// Get withdrawals
277    ///
278    /// Retrieves the latest user withdrawals.
279    ///
280    /// # Arguments
281    ///
282    /// * `currency` - Currency symbol (BTC, ETH, etc.)
283    /// * `count` - Number of requested items (optional, default 10)
284    /// * `offset` - Offset for pagination (optional, default 0)
285    ///
286    /// # Examples
287    ///
288    /// ```rust
289    /// use deribit_http::DeribitHttpClient;
290    ///
291    /// let client = DeribitHttpClient::new();
292    /// // let withdrawals = client.get_withdrawals("BTC", Some(20), Some(0)).await?;
293    /// // tracing::info!("Found {} withdrawals", withdrawals.data.len());
294    /// ```
295    pub async fn get_withdrawals(
296        &self,
297        currency: &str,
298        count: Option<u32>,
299        offset: Option<u32>,
300    ) -> Result<WithdrawalsResponse, HttpError> {
301        let mut query_params = vec![("currency".to_string(), currency.to_string())];
302
303        if let Some(count) = count {
304            query_params.push(("count".to_string(), count.to_string()));
305        }
306
307        if let Some(offset) = offset {
308            query_params.push(("offset".to_string(), offset.to_string()));
309        }
310
311        let query_string = query_params
312            .iter()
313            .map(|(k, v)| format!("{}={}", k, urlencoding::encode(v)))
314            .collect::<Vec<_>>()
315            .join("&");
316
317        let url = format!("{}{}?{}", self.base_url(), GET_WITHDRAWALS, query_string);
318
319        let response = self.make_authenticated_request(&url).await?;
320
321        if !response.status().is_success() {
322            let error_text = response
323                .text()
324                .await
325                .unwrap_or_else(|_| "Unknown error".to_string());
326            return Err(HttpError::RequestFailed(format!(
327                "Get withdrawals failed: {}",
328                error_text
329            )));
330        }
331
332        let api_response: ApiResponse<WithdrawalsResponse> = response
333            .json()
334            .await
335            .map_err(|e| HttpError::InvalidResponse(e.to_string()))?;
336
337        if let Some(error) = api_response.error {
338            return Err(HttpError::RequestFailed(format!(
339                "API error: {} - {}",
340                error.code, error.message
341            )));
342        }
343
344        api_response.result.ok_or_else(|| {
345            HttpError::InvalidResponse("No withdrawals data in response".to_string())
346        })
347    }
348
349    /// Submit transfer to subaccount
350    ///
351    /// Transfers funds to a subaccount.
352    ///
353    /// # Arguments
354    ///
355    /// * `currency` - Currency symbol (BTC, ETH, etc.)
356    /// * `amount` - Amount of funds to be transferred
357    /// * `destination` - ID of destination subaccount
358    ///
359    /// # Examples
360    ///
361    /// ```rust
362    /// use deribit_http::DeribitHttpClient;
363    ///
364    /// let client = DeribitHttpClient::new();
365    /// // let transfer = client.submit_transfer_to_subaccount("BTC", 0.001, 123).await?;
366    /// // tracing::info!("Transfer ID: {}", transfer.id);
367    /// ```
368    pub async fn submit_transfer_to_subaccount(
369        &self,
370        currency: &str,
371        amount: f64,
372        destination: u64,
373    ) -> Result<TransferResultResponse, HttpError> {
374        let query_params = [
375            ("currency".to_string(), currency.to_string()),
376            ("amount".to_string(), amount.to_string()),
377            ("destination".to_string(), destination.to_string()),
378        ];
379
380        let query_string = query_params
381            .iter()
382            .map(|(k, v)| format!("{}={}", k, urlencoding::encode(v)))
383            .collect::<Vec<_>>()
384            .join("&");
385
386        let url = format!(
387            "{}{}?{}",
388            self.base_url(),
389            SUBMIT_TRANSFER_TO_SUBACCOUNT,
390            query_string
391        );
392
393        let response = self.make_authenticated_request(&url).await?;
394
395        if !response.status().is_success() {
396            let error_text = response
397                .text()
398                .await
399                .unwrap_or_else(|_| "Unknown error".to_string());
400            return Err(HttpError::RequestFailed(format!(
401                "Submit transfer to subaccount failed: {}",
402                error_text
403            )));
404        }
405
406        let api_response: ApiResponse<TransferResultResponse> = response
407            .json()
408            .await
409            .map_err(|e| HttpError::InvalidResponse(e.to_string()))?;
410
411        if let Some(error) = api_response.error {
412            return Err(HttpError::RequestFailed(format!(
413                "API error: {} - {}",
414                error.code, error.message
415            )));
416        }
417
418        api_response
419            .result
420            .ok_or_else(|| HttpError::InvalidResponse("No transfer result in response".to_string()))
421    }
422
423    /// Submit transfer to user
424    ///
425    /// Transfers funds to another user.
426    ///
427    /// # Arguments
428    ///
429    /// * `currency` - Currency symbol (BTC, ETH, etc.)
430    /// * `amount` - Amount of funds to be transferred
431    /// * `destination` - Destination wallet address from address book
432    ///
433    /// # Examples
434    ///
435    /// ```rust
436    /// use deribit_http::DeribitHttpClient;
437    ///
438    /// let client = DeribitHttpClient::new();
439    /// // let transfer = client.submit_transfer_to_user("ETH", 0.1, "0x1234...").await?;
440    /// // tracing::info!("Transfer ID: {}", transfer.id);
441    /// ```
442    pub async fn submit_transfer_to_user(
443        &self,
444        currency: &str,
445        amount: f64,
446        destination: &str,
447    ) -> Result<TransferResultResponse, HttpError> {
448        let query_params = [
449            ("currency".to_string(), currency.to_string()),
450            ("amount".to_string(), amount.to_string()),
451            ("destination".to_string(), destination.to_string()),
452        ];
453
454        let query_string = query_params
455            .iter()
456            .map(|(k, v)| format!("{}={}", k, urlencoding::encode(v)))
457            .collect::<Vec<_>>()
458            .join("&");
459
460        let url = format!(
461            "{}{}?{}",
462            self.base_url(),
463            SUBMIT_TRANSFER_TO_USER,
464            query_string
465        );
466
467        let response = self.make_authenticated_request(&url).await?;
468
469        if !response.status().is_success() {
470            let error_text = response
471                .text()
472                .await
473                .unwrap_or_else(|_| "Unknown error".to_string());
474            return Err(HttpError::RequestFailed(format!(
475                "Submit transfer to user failed: {}",
476                error_text
477            )));
478        }
479
480        let api_response: ApiResponse<TransferResultResponse> = response
481            .json()
482            .await
483            .map_err(|e| HttpError::InvalidResponse(e.to_string()))?;
484
485        if let Some(error) = api_response.error {
486            return Err(HttpError::RequestFailed(format!(
487                "API error: {} - {}",
488                error.code, error.message
489            )));
490        }
491
492        api_response
493            .result
494            .ok_or_else(|| HttpError::InvalidResponse("No transfer result in response".to_string()))
495    }
496
497    /// Place a buy order
498    ///
499    /// Places a buy order for the specified instrument.
500    ///
501    /// # Arguments
502    ///
503    /// * `request` - The buy order request parameters
504    ///
505    pub async fn buy_order(&self, request: OrderRequest) -> Result<OrderResponse, HttpError> {
506        let mut query_params = vec![
507            ("instrument_name".to_string(), request.instrument_name),
508            (
509                "amount".to_string(),
510                request
511                    .amount
512                    .map_or_else(|| "0".to_string(), |a| a.to_string()),
513            ),
514        ];
515
516        if let Some(order_type) = request.type_ {
517            query_params.push(("type".to_string(), order_type.as_str().to_string()));
518        }
519
520        if let Some(price) = request.price {
521            query_params.push(("price".to_string(), price.to_string()));
522        }
523
524        if let Some(label) = request.label {
525            query_params.push(("label".to_string(), label));
526        }
527
528        if let Some(time_in_force) = request.time_in_force {
529            query_params.push((
530                "time_in_force".to_string(),
531                time_in_force.as_str().to_string(),
532            ));
533        }
534
535        if let Some(post_only) = request.post_only
536            && post_only
537        {
538            query_params.push(("post_only".to_string(), "true".to_string()));
539        }
540
541        if let Some(reduce_only) = request.reduce_only
542            && reduce_only
543        {
544            query_params.push(("reduce_only".to_string(), "true".to_string()));
545        }
546
547        if let Some(trigger_price) = request.trigger_price {
548            query_params.push(("trigger_price".to_string(), trigger_price.to_string()));
549        }
550
551        if let Some(trigger) = request.trigger {
552            let trigger_str = match trigger {
553                Trigger::IndexPrice => "index_price",
554                Trigger::MarkPrice => "mark_price",
555                Trigger::LastPrice => "last_price",
556            };
557            query_params.push(("trigger".to_string(), trigger_str.to_string()));
558        }
559
560        let query_string = query_params
561            .iter()
562            .map(|(k, v)| format!("{}={}", k, urlencoding::encode(v)))
563            .collect::<Vec<_>>()
564            .join("&");
565
566        let url = format!("{}{}?{}", self.base_url(), BUY, query_string);
567
568        let response = self.make_authenticated_request(&url).await?;
569
570        if !response.status().is_success() {
571            let error_text = response
572                .text()
573                .await
574                .unwrap_or_else(|_| "Unknown error".to_string());
575            return Err(HttpError::RequestFailed(format!(
576                "Buy order failed: {}",
577                error_text
578            )));
579        }
580
581        // Debug: capture raw response text first
582        let response_text = response
583            .text()
584            .await
585            .map_err(|e| HttpError::NetworkError(e.to_string()))?;
586
587        tracing::debug!("Raw API response: {}", response_text);
588
589        let api_response: ApiResponse<OrderResponse> = serde_json::from_str(&response_text)
590            .map_err(|e| {
591                HttpError::InvalidResponse(format!(
592                    "Failed to parse JSON: {} - Raw response: {}",
593                    e, response_text
594                ))
595            })?;
596
597        if let Some(error) = api_response.error {
598            return Err(HttpError::RequestFailed(format!(
599                "API error: {} - {}",
600                error.code, error.message
601            )));
602        }
603
604        api_response
605            .result
606            .ok_or_else(|| HttpError::InvalidResponse("No order data in response".to_string()))
607    }
608
609    /// Place a sell order
610    ///
611    /// Places a sell order for the specified instrument.
612    ///
613    /// # Arguments
614    ///
615    /// * `request` - The sell order request parameters
616    pub async fn sell_order(&self, request: OrderRequest) -> Result<OrderResponse, HttpError> {
617        let mut query_params = vec![
618            ("instrument_name".to_string(), request.instrument_name),
619            ("amount".to_string(), request.amount.unwrap().to_string()),
620        ];
621
622        if let Some(order_type) = request.type_ {
623            query_params.push(("type".to_string(), order_type.as_str().to_string()));
624        }
625
626        if let Some(price) = request.price {
627            query_params.push(("price".to_string(), price.to_string()));
628        }
629
630        if let Some(label) = request.label {
631            query_params.push(("label".to_string(), label));
632        }
633
634        if let Some(time_in_force) = request.time_in_force {
635            query_params.push((
636                "time_in_force".to_string(),
637                time_in_force.as_str().to_string(),
638            ));
639        }
640
641        if let Some(post_only) = request.post_only
642            && post_only
643        {
644            query_params.push(("post_only".to_string(), "true".to_string()));
645        }
646
647        if let Some(reduce_only) = request.reduce_only
648            && reduce_only
649        {
650            query_params.push(("reduce_only".to_string(), "true".to_string()));
651        }
652
653        if let Some(trigger_price) = request.trigger_price {
654            query_params.push(("trigger_price".to_string(), trigger_price.to_string()));
655        }
656
657        if let Some(trigger) = request.trigger {
658            let trigger_str = match trigger {
659                Trigger::IndexPrice => "index_price",
660                Trigger::MarkPrice => "mark_price",
661                Trigger::LastPrice => "last_price",
662            };
663            query_params.push(("trigger".to_string(), trigger_str.to_string()));
664        }
665
666        let query_string = query_params
667            .iter()
668            .map(|(k, v)| format!("{}={}", k, urlencoding::encode(v)))
669            .collect::<Vec<_>>()
670            .join("&");
671
672        let url = format!("{}{}?{}", self.base_url(), SELL, query_string);
673
674        let response = self.make_authenticated_request(&url).await?;
675
676        if !response.status().is_success() {
677            let error_text = response
678                .text()
679                .await
680                .unwrap_or_else(|_| "Unknown error".to_string());
681            return Err(HttpError::RequestFailed(format!(
682                "Sell order failed: {}",
683                error_text
684            )));
685        }
686
687        let api_response: ApiResponse<OrderResponse> = response
688            .json()
689            .await
690            .map_err(|e| HttpError::InvalidResponse(e.to_string()))?;
691
692        if let Some(error) = api_response.error {
693            return Err(HttpError::RequestFailed(format!(
694                "API error: {} - {}",
695                error.code, error.message
696            )));
697        }
698
699        api_response
700            .result
701            .ok_or_else(|| HttpError::InvalidResponse("No order data in response".to_string()))
702    }
703
704    /// Cancel an order
705    ///
706    /// Cancels an order by its ID.
707    ///
708    /// # Arguments
709    ///
710    /// * `order_id` - The order ID to cancel
711    ///
712    pub async fn cancel_order(&self, order_id: &str) -> Result<OrderInfoResponse, HttpError> {
713        let url = format!(
714            "{}{}?order_id={}",
715            self.base_url(),
716            CANCEL,
717            urlencoding::encode(order_id)
718        );
719
720        let response = self.make_authenticated_request(&url).await?;
721
722        if !response.status().is_success() {
723            let error_text = response
724                .text()
725                .await
726                .unwrap_or_else(|_| "Unknown error".to_string());
727            return Err(HttpError::RequestFailed(format!(
728                "Cancel order failed: {}",
729                error_text
730            )));
731        }
732
733        let api_response: ApiResponse<OrderInfoResponse> = response
734            .json()
735            .await
736            .map_err(|e| HttpError::InvalidResponse(e.to_string()))?;
737
738        if let Some(error) = api_response.error {
739            return Err(HttpError::RequestFailed(format!(
740                "API error: {} - {}",
741                error.code, error.message
742            )));
743        }
744
745        api_response
746            .result
747            .ok_or_else(|| HttpError::InvalidResponse("No order data in response".to_string()))
748    }
749
750    /// Cancel all orders
751    ///
752    /// Cancels all orders for the account.
753    ///
754    /// # Returns
755    ///
756    /// Returns the number of cancelled orders.
757    pub async fn cancel_all(&self) -> Result<u32, HttpError> {
758        let url = format!("{}{}", self.base_url(), CANCEL_ALL);
759
760        let response = self.make_authenticated_request(&url).await?;
761
762        if !response.status().is_success() {
763            let error_text = response
764                .text()
765                .await
766                .unwrap_or_else(|_| "Unknown error".to_string());
767            return Err(HttpError::RequestFailed(format!(
768                "Cancel all orders failed: {}",
769                error_text
770            )));
771        }
772
773        let api_response: ApiResponse<u32> = response
774            .json()
775            .await
776            .map_err(|e| HttpError::InvalidResponse(e.to_string()))?;
777
778        if let Some(error) = api_response.error {
779            return Err(HttpError::RequestFailed(format!(
780                "API error: {} - {}",
781                error.code, error.message
782            )));
783        }
784
785        api_response
786            .result
787            .ok_or_else(|| HttpError::InvalidResponse("No result in response".to_string()))
788    }
789
790    /// Cancel all orders by currency
791    ///
792    /// Cancels all orders for the specified currency.
793    ///
794    /// # Arguments
795    ///
796    /// * `currency` - Currency to cancel orders for (BTC, ETH, USDC, etc.)
797    ///
798    /// # Returns
799    ///
800    /// Returns the number of cancelled orders.
801    pub async fn cancel_all_by_currency(&self, currency: &str) -> Result<u32, HttpError> {
802        let url = format!(
803            "{}{}?currency={}",
804            self.base_url(),
805            CANCEL_ALL_BY_CURRENCY,
806            urlencoding::encode(currency)
807        );
808
809        let response = self.make_authenticated_request(&url).await?;
810
811        if !response.status().is_success() {
812            let error_text = response
813                .text()
814                .await
815                .unwrap_or_else(|_| "Unknown error".to_string());
816            return Err(HttpError::RequestFailed(format!(
817                "Cancel all orders by currency failed: {}",
818                error_text
819            )));
820        }
821
822        let api_response: ApiResponse<u32> = response
823            .json()
824            .await
825            .map_err(|e| HttpError::InvalidResponse(e.to_string()))?;
826
827        if let Some(error) = api_response.error {
828            return Err(HttpError::RequestFailed(format!(
829                "API error: {} - {}",
830                error.code, error.message
831            )));
832        }
833
834        api_response
835            .result
836            .ok_or_else(|| HttpError::InvalidResponse("No result in response".to_string()))
837    }
838
839    /// Cancel all orders by currency pair
840    ///
841    /// Cancels all orders for the specified currency pair.
842    ///
843    /// # Arguments
844    ///
845    /// * `currency_pair` - Currency pair to cancel orders for (e.g., "BTC_USD")
846    ///
847    /// # Returns
848    ///
849    /// Returns the number of cancelled orders.
850    pub async fn cancel_all_by_currency_pair(&self, currency_pair: &str) -> Result<u32, HttpError> {
851        let url = format!(
852            "{}{}?currency_pair={}",
853            self.base_url(),
854            CANCEL_ALL_BY_CURRENCY_PAIR,
855            urlencoding::encode(currency_pair)
856        );
857
858        let response = self.make_authenticated_request(&url).await?;
859
860        if !response.status().is_success() {
861            let error_text = response
862                .text()
863                .await
864                .unwrap_or_else(|_| "Unknown error".to_string());
865            return Err(HttpError::RequestFailed(format!(
866                "Cancel all orders by currency pair failed: {}",
867                error_text
868            )));
869        }
870
871        let api_response: ApiResponse<u32> = response
872            .json()
873            .await
874            .map_err(|e| HttpError::InvalidResponse(e.to_string()))?;
875
876        if let Some(error) = api_response.error {
877            return Err(HttpError::RequestFailed(format!(
878                "API error: {} - {}",
879                error.code, error.message
880            )));
881        }
882
883        api_response
884            .result
885            .ok_or_else(|| HttpError::InvalidResponse("No result in response".to_string()))
886    }
887
888    /// Cancel all orders by instrument
889    ///
890    /// Cancels all orders for the specified instrument.
891    ///
892    /// # Arguments
893    ///
894    /// * `instrument_name` - Instrument name to cancel orders for (e.g., "BTC-PERPETUAL")
895    ///
896    /// # Returns
897    ///
898    /// Returns the number of cancelled orders.
899    pub async fn cancel_all_by_instrument(&self, instrument_name: &str) -> Result<u32, HttpError> {
900        let url = format!(
901            "{}{}?instrument_name={}",
902            self.base_url(),
903            CANCEL_ALL_BY_INSTRUMENT,
904            urlencoding::encode(instrument_name)
905        );
906
907        let response = self.make_authenticated_request(&url).await?;
908
909        if !response.status().is_success() {
910            let error_text = response
911                .text()
912                .await
913                .unwrap_or_else(|_| "Unknown error".to_string());
914            return Err(HttpError::RequestFailed(format!(
915                "Cancel all orders by instrument failed: {}",
916                error_text
917            )));
918        }
919
920        let api_response: ApiResponse<u32> = response
921            .json()
922            .await
923            .map_err(|e| HttpError::InvalidResponse(e.to_string()))?;
924
925        if let Some(error) = api_response.error {
926            return Err(HttpError::RequestFailed(format!(
927                "API error: {} - {}",
928                error.code, error.message
929            )));
930        }
931
932        api_response
933            .result
934            .ok_or_else(|| HttpError::InvalidResponse("No result in response".to_string()))
935    }
936
937    /// Cancel all orders by kind or type
938    ///
939    /// Cancels all orders for the specified kind or type.
940    ///
941    /// # Arguments
942    ///
943    /// * `kind` - Kind of orders to cancel (future, option, spot, etc.) - optional
944    /// * `order_type` - Type of orders to cancel (limit, market, etc.) - optional
945    ///
946    /// # Returns
947    ///
948    /// Returns the number of cancelled orders.
949    pub async fn cancel_all_by_kind_or_type(
950        &self,
951        kind: Option<&str>,
952        order_type: Option<&str>,
953    ) -> Result<u32, HttpError> {
954        let mut query_params = Vec::new();
955
956        if let Some(kind) = kind {
957            query_params.push(("kind".to_string(), kind.to_string()));
958        }
959
960        if let Some(order_type) = order_type {
961            query_params.push(("type".to_string(), order_type.to_string()));
962        }
963
964        let query_string = if query_params.is_empty() {
965            String::new()
966        } else {
967            "?".to_string()
968                + &query_params
969                    .iter()
970                    .map(|(k, v)| format!("{}={}", k, urlencoding::encode(v)))
971                    .collect::<Vec<_>>()
972                    .join("&")
973        };
974
975        let url = format!(
976            "{}{}{}",
977            self.base_url(),
978            CANCEL_ALL_BY_KIND_OR_TYPE,
979            query_string
980        );
981
982        let response = self.make_authenticated_request(&url).await?;
983
984        if !response.status().is_success() {
985            let error_text = response
986                .text()
987                .await
988                .unwrap_or_else(|_| "Unknown error".to_string());
989            return Err(HttpError::RequestFailed(format!(
990                "Cancel all orders by kind or type failed: {}",
991                error_text
992            )));
993        }
994
995        let api_response: ApiResponse<u32> = response
996            .json()
997            .await
998            .map_err(|e| HttpError::InvalidResponse(e.to_string()))?;
999
1000        if let Some(error) = api_response.error {
1001            return Err(HttpError::RequestFailed(format!(
1002                "API error: {} - {}",
1003                error.code, error.message
1004            )));
1005        }
1006
1007        api_response
1008            .result
1009            .ok_or_else(|| HttpError::InvalidResponse("No result in response".to_string()))
1010    }
1011
1012    /// Cancel orders by label
1013    ///
1014    /// Cancels all orders with the specified label.
1015    ///
1016    /// # Arguments
1017    ///
1018    /// * `label` - Label of orders to cancel
1019    ///
1020    /// # Returns
1021    ///
1022    /// Returns the number of cancelled orders.
1023    pub async fn cancel_by_label(&self, label: &str) -> Result<u32, HttpError> {
1024        let url = format!(
1025            "{}{}?label={}",
1026            self.base_url(),
1027            CANCEL_BY_LABEL,
1028            urlencoding::encode(label)
1029        );
1030
1031        let response = self.make_authenticated_request(&url).await?;
1032
1033        if !response.status().is_success() {
1034            let error_text = response
1035                .text()
1036                .await
1037                .unwrap_or_else(|_| "Unknown error".to_string());
1038            return Err(HttpError::RequestFailed(format!(
1039                "Cancel orders by label failed: {}",
1040                error_text
1041            )));
1042        }
1043
1044        let api_response: ApiResponse<u32> = response
1045            .json()
1046            .await
1047            .map_err(|e| HttpError::InvalidResponse(e.to_string()))?;
1048
1049        if let Some(error) = api_response.error {
1050            return Err(HttpError::RequestFailed(format!(
1051                "API error: {} - {}",
1052                error.code, error.message
1053            )));
1054        }
1055
1056        api_response
1057            .result
1058            .ok_or_else(|| HttpError::InvalidResponse("No result in response".to_string()))
1059    }
1060
1061    /// Get account summary
1062    ///
1063    /// Retrieves account summary information including balance, margin, and other account details.
1064    ///
1065    /// # Arguments
1066    ///
1067    /// * `currency` - Currency to get summary for (BTC, ETH, USDC, etc.)
1068    /// * `extended` - Whether to include extended information
1069    ///
1070    pub async fn get_account_summary(
1071        &self,
1072        currency: &str,
1073        extended: Option<bool>,
1074    ) -> Result<AccountSummaryResponse, HttpError> {
1075        let mut query_params = vec![("currency".to_string(), currency.to_string())];
1076
1077        if let Some(extended) = extended {
1078            query_params.push(("extended".to_string(), extended.to_string()));
1079        }
1080
1081        let query_string = query_params
1082            .iter()
1083            .map(|(k, v)| format!("{}={}", k, urlencoding::encode(v)))
1084            .collect::<Vec<_>>()
1085            .join("&");
1086
1087        let url = format!(
1088            "{}{}?{}",
1089            self.base_url(),
1090            GET_ACCOUNT_SUMMARY,
1091            query_string
1092        );
1093
1094        let response = self.make_authenticated_request(&url).await?;
1095
1096        if !response.status().is_success() {
1097            let error_text = response
1098                .text()
1099                .await
1100                .unwrap_or_else(|_| "Unknown error".to_string());
1101            return Err(HttpError::RequestFailed(format!(
1102                "Get account summary failed: {}",
1103                error_text
1104            )));
1105        }
1106
1107        let api_response: ApiResponse<AccountSummaryResponse> = response
1108            .json()
1109            .await
1110            .map_err(|e| HttpError::InvalidResponse(e.to_string()))?;
1111
1112        if let Some(error) = api_response.error {
1113            return Err(HttpError::RequestFailed(format!(
1114                "API error: {} - {}",
1115                error.code, error.message
1116            )));
1117        }
1118
1119        api_response.result.ok_or_else(|| {
1120            HttpError::InvalidResponse("No account summary data in response".to_string())
1121        })
1122    }
1123
1124    /// Get positions
1125    ///
1126    /// Retrieves user positions for the specified currency and kind.
1127    ///
1128    /// # Arguments
1129    ///
1130    /// * `currency` - Currency filter (BTC, ETH, USDC, etc.) - optional
1131    /// * `kind` - Kind filter (future, option, spot, etc.) - optional
1132    /// * `subaccount_id` - Subaccount ID - optional
1133    ///
1134    /// # Examples
1135    ///
1136    /// ```rust
1137    /// use deribit_http::DeribitHttpClient;
1138    ///
1139    /// let client = DeribitHttpClient::new();
1140    /// // let positions = client.get_positions(Some("BTC"), Some("future"), None).await?;
1141    /// // println!("Found {} positions", positions.len());
1142    /// ```
1143    pub async fn get_positions(
1144        &self,
1145        currency: Option<&str>,
1146        kind: Option<&str>,
1147        subaccount_id: Option<i32>,
1148    ) -> Result<Vec<Position>, HttpError> {
1149        let mut query_params = Vec::new();
1150
1151        if let Some(currency) = currency {
1152            query_params.push(("currency".to_string(), currency.to_string()));
1153        }
1154
1155        if let Some(kind) = kind {
1156            query_params.push(("kind".to_string(), kind.to_string()));
1157        }
1158
1159        if let Some(subaccount_id) = subaccount_id {
1160            query_params.push(("subaccount_id".to_string(), subaccount_id.to_string()));
1161        }
1162
1163        let query_string = if query_params.is_empty() {
1164            String::new()
1165        } else {
1166            "?".to_string()
1167                + &query_params
1168                    .iter()
1169                    .map(|(k, v)| format!("{}={}", k, urlencoding::encode(v)))
1170                    .collect::<Vec<_>>()
1171                    .join("&")
1172        };
1173
1174        let url = format!("{}{}{}", self.base_url(), GET_POSITIONS, query_string);
1175
1176        let response = self.make_authenticated_request(&url).await?;
1177
1178        if !response.status().is_success() {
1179            let error_text = response
1180                .text()
1181                .await
1182                .unwrap_or_else(|_| "Unknown error".to_string());
1183            return Err(HttpError::RequestFailed(format!(
1184                "Get positions failed: {}",
1185                error_text
1186            )));
1187        }
1188
1189        let api_response: ApiResponse<Vec<Position>> = response
1190            .json()
1191            .await
1192            .map_err(|e| HttpError::InvalidResponse(e.to_string()))?;
1193
1194        if let Some(error) = api_response.error {
1195            return Err(HttpError::RequestFailed(format!(
1196                "API error: {} - {}",
1197                error.code, error.message
1198            )));
1199        }
1200
1201        api_response
1202            .result
1203            .ok_or_else(|| HttpError::InvalidResponse("No positions data in response".to_string()))
1204    }
1205
1206    /// Get position for a specific instrument
1207    ///
1208    /// Retrieves the current position for the specified instrument.
1209    ///
1210    /// # Arguments
1211    ///
1212    /// * `instrument_name` - The name of the instrument to get position for
1213    ///
1214    /// # Returns
1215    ///
1216    /// Returns a vector of positions for the specified instrument
1217    ///
1218    pub async fn get_position(&self, instrument_name: &str) -> Result<Vec<Position>, HttpError> {
1219        let query_string = format!("instrument_name={}", instrument_name);
1220        let url = format!("{}{}{}", self.base_url(), GET_POSITION, query_string);
1221        let response = self.make_authenticated_request(&url).await?;
1222
1223        if !response.status().is_success() {
1224            let error_text = response
1225                .text()
1226                .await
1227                .unwrap_or_else(|_| "Unknown error".to_string());
1228            return Err(HttpError::RequestFailed(format!(
1229                "Get positions failed: {}",
1230                error_text
1231            )));
1232        }
1233
1234        let api_response: ApiResponse<Vec<Position>> = response
1235            .json()
1236            .await
1237            .map_err(|e| HttpError::InvalidResponse(e.to_string()))?;
1238
1239        if let Some(error) = api_response.error {
1240            return Err(HttpError::RequestFailed(format!(
1241                "API error: {} - {}",
1242                error.code, error.message
1243            )));
1244        }
1245
1246        api_response
1247            .result
1248            .ok_or_else(|| HttpError::InvalidResponse("No positions data in response".to_string()))
1249    }
1250
1251    /// Edit an order
1252    ///
1253    /// Edits an existing order.
1254    ///
1255    /// # Arguments
1256    ///
1257    /// * `request` - The edit order request parameters
1258    ///
1259    pub async fn edit_order(&self, request: OrderRequest) -> Result<OrderResponse, HttpError> {
1260        let order_id = request.order_id.ok_or_else(|| {
1261            HttpError::RequestFailed("order_id is required for edit_order".to_string())
1262        })?;
1263        let mut query_params = vec![("order_id", order_id.as_str())];
1264
1265        let amount_str;
1266        if let Some(amount) = request.amount {
1267            amount_str = amount.to_string();
1268            query_params.push(("amount", amount_str.as_str()));
1269        }
1270
1271        let price_str;
1272        if let Some(price) = request.price {
1273            price_str = price.to_string();
1274            query_params.push(("price", price_str.as_str()));
1275        }
1276
1277        if let Some(post_only) = request.post_only
1278            && post_only
1279        {
1280            query_params.push(("post_only", "true"));
1281        }
1282
1283        if let Some(reduce_only) = request.reduce_only
1284            && reduce_only
1285        {
1286            query_params.push(("reduce_only", "true"));
1287        }
1288
1289        let query_string = query_params
1290            .iter()
1291            .map(|(k, v)| format!("{}={}", k, urlencoding::encode(v)))
1292            .collect::<Vec<_>>()
1293            .join("&");
1294
1295        let url = format!("{}{}?{}", self.base_url(), EDIT, query_string);
1296
1297        let response = self.make_authenticated_request(&url).await?;
1298
1299        if !response.status().is_success() {
1300            let error_text = response
1301                .text()
1302                .await
1303                .unwrap_or_else(|_| "Unknown error".to_string());
1304            return Err(HttpError::RequestFailed(format!(
1305                "Edit order failed: {}",
1306                error_text
1307            )));
1308        }
1309
1310        let api_response: ApiResponse<OrderResponse> = response
1311            .json()
1312            .await
1313            .map_err(|e| HttpError::InvalidResponse(e.to_string()))?;
1314
1315        if let Some(error) = api_response.error {
1316            return Err(HttpError::RequestFailed(format!(
1317                "API error: {} - {}",
1318                error.code, error.message
1319            )));
1320        }
1321
1322        api_response
1323            .result
1324            .ok_or_else(|| HttpError::InvalidResponse("No order data in response".to_string()))
1325    }
1326
1327    // TODO:
1328    // pub async fn edit_order_by_label(&self, request: OrderRequest) -> Result<OrderResponse, HttpError> {
1329    //
1330    // }
1331
1332    /// Mass quote
1333    ///
1334    /// Places multiple quotes at once.
1335    ///
1336    /// # Arguments
1337    ///
1338    /// * `quotes` - Vector of mass quote requests
1339    ///
1340    pub async fn mass_quote(
1341        &self,
1342        _quotes: MassQuoteRequest,
1343    ) -> Result<MassQuoteResponse, HttpError> {
1344        Err(HttpError::ConfigError(
1345            "Mass quote endpoint is only available via WebSocket connections. \
1346             According to Deribit's technical specifications, private/mass_quote requires \
1347             WebSocket for real-time quote management, MMP group integration, and \
1348             Cancel-on-Disconnect functionality. Please use the deribit-websocket client \
1349             for mass quote operations."
1350                .to_string(),
1351        ))
1352    }
1353
1354    /// Get user trades by instrument
1355    ///
1356    /// Retrieves user trades for a specific instrument.
1357    ///
1358    /// # Arguments
1359    ///
1360    /// * `instrument_name` - Instrument name
1361    /// * `start_seq` - Start sequence number (optional)
1362    /// * `end_seq` - End sequence number (optional)
1363    /// * `count` - Number of requested items (optional)
1364    /// * `include_old` - Include old trades (optional)
1365    /// * `sorting` - Direction of results sorting (optional)
1366    ///
1367    pub async fn get_user_trades_by_instrument(
1368        &self,
1369        instrument_name: &str,
1370        start_seq: Option<u64>,
1371        end_seq: Option<u64>,
1372        count: Option<u32>,
1373        include_old: Option<bool>,
1374        sorting: Option<&str>,
1375    ) -> Result<UserTradeWithPaginationResponse, HttpError> {
1376        let mut query_params = vec![("instrument_name".to_string(), instrument_name.to_string())];
1377
1378        if let Some(start_seq) = start_seq {
1379            query_params.push(("start_seq".to_string(), start_seq.to_string()));
1380        }
1381
1382        if let Some(end_seq) = end_seq {
1383            query_params.push(("end_seq".to_string(), end_seq.to_string()));
1384        }
1385
1386        if let Some(count) = count {
1387            query_params.push(("count".to_string(), count.to_string()));
1388        }
1389
1390        if let Some(include_old) = include_old {
1391            query_params.push(("include_old".to_string(), include_old.to_string()));
1392        }
1393
1394        if let Some(sorting) = sorting {
1395            query_params.push(("sorting".to_string(), sorting.to_string()));
1396        }
1397
1398        let query_string = query_params
1399            .iter()
1400            .map(|(k, v)| format!("{}={}", k, urlencoding::encode(v)))
1401            .collect::<Vec<_>>()
1402            .join("&");
1403
1404        let url = format!(
1405            "{}{}?{}",
1406            self.base_url(),
1407            GET_USER_TRADES_BY_INSTRUMENT,
1408            query_string
1409        );
1410
1411        let response = self.make_authenticated_request(&url).await?;
1412
1413        if !response.status().is_success() {
1414            let error_text = response
1415                .text()
1416                .await
1417                .unwrap_or_else(|_| "Unknown error".to_string());
1418            return Err(HttpError::RequestFailed(format!(
1419                "Get user trades by instrument failed: {}",
1420                error_text
1421            )));
1422        }
1423
1424        // Debug: Log the raw response text before trying to parse it
1425        let response_text = response.text().await.map_err(|e| {
1426            HttpError::InvalidResponse(format!("Failed to read response text: {}", e))
1427        })?;
1428
1429        tracing::debug!(
1430            "Raw API response for get_user_trades_by_instrument: {}",
1431            response_text
1432        );
1433
1434        // Try to parse as JSON
1435        let api_response: ApiResponse<UserTradeWithPaginationResponse> =
1436            serde_json::from_str(&response_text).map_err(|e| {
1437                HttpError::InvalidResponse(format!(
1438                    "error decoding response body: {} - Raw response: {}",
1439                    e, response_text
1440                ))
1441            })?;
1442
1443        if let Some(error) = api_response.error {
1444            return Err(HttpError::RequestFailed(format!(
1445                "API error: {} - {}",
1446                error.code, error.message
1447            )));
1448        }
1449
1450        api_response.result.ok_or_else(|| {
1451            HttpError::InvalidResponse("No user trades data in response".to_string())
1452        })
1453    }
1454
1455    /// Cancel quotes
1456    ///
1457    /// Cancels all mass quotes.
1458    ///
1459    /// # Arguments
1460    ///
1461    /// * `cancel_type` - Type of cancellation ("all", "by_currency", "by_instrument", etc.)
1462    ///
1463    pub async fn cancel_quotes(&self, cancel_type: Option<&str>) -> Result<u32, HttpError> {
1464        let mut url = format!("{}{}", self.base_url(), CANCEL_QUOTES);
1465
1466        if let Some(cancel_type) = cancel_type {
1467            url.push_str(&format!(
1468                "?cancel_type={}",
1469                urlencoding::encode(cancel_type)
1470            ));
1471        } else {
1472            url.push_str("?cancel_type=all");
1473        }
1474
1475        let response = self.make_authenticated_request(&url).await?;
1476
1477        if !response.status().is_success() {
1478            let error_text = response
1479                .text()
1480                .await
1481                .unwrap_or_else(|_| "Unknown error".to_string());
1482            return Err(HttpError::RequestFailed(format!(
1483                "Cancel quotes failed: {}",
1484                error_text
1485            )));
1486        }
1487
1488        let api_response: ApiResponse<u32> = response
1489            .json()
1490            .await
1491            .map_err(|e| HttpError::InvalidResponse(e.to_string()))?;
1492
1493        if let Some(error) = api_response.error {
1494            return Err(HttpError::RequestFailed(format!(
1495                "API error: {} - {}",
1496                error.code, error.message
1497            )));
1498        }
1499
1500        api_response
1501            .result
1502            .ok_or_else(|| HttpError::InvalidResponse("No cancel result in response".to_string()))
1503    }
1504
1505    /// Get open orders
1506    ///
1507    /// Retrieves list of user's open orders across many currencies.
1508    ///
1509    /// # Arguments
1510    ///
1511    /// * `kind` - Instrument kind filter (optional)
1512    /// * `order_type` - Order type filter (optional)
1513    ///
1514    pub async fn get_open_orders(
1515        &self,
1516        kind: Option<&str>,
1517        order_type: Option<&str>,
1518    ) -> Result<Vec<OrderInfoResponse>, HttpError> {
1519        let mut query_params = Vec::new();
1520
1521        if let Some(kind) = kind {
1522            query_params.push(("kind".to_string(), kind.to_string()));
1523        }
1524
1525        if let Some(order_type) = order_type {
1526            query_params.push(("type".to_string(), order_type.to_string()));
1527        }
1528
1529        let query_string = if query_params.is_empty() {
1530            String::new()
1531        } else {
1532            "?".to_string()
1533                + &query_params
1534                    .iter()
1535                    .map(|(k, v)| format!("{}={}", k, urlencoding::encode(v)))
1536                    .collect::<Vec<_>>()
1537                    .join("&")
1538        };
1539
1540        let url = format!("{}{}{}", self.base_url(), GET_OPEN_ORDERS, query_string);
1541
1542        let response = self.make_authenticated_request(&url).await?;
1543
1544        if !response.status().is_success() {
1545            let error_text = response
1546                .text()
1547                .await
1548                .unwrap_or_else(|_| "Unknown error".to_string());
1549            return Err(HttpError::RequestFailed(format!(
1550                "Get open orders failed: {}",
1551                error_text
1552            )));
1553        }
1554
1555        let api_response: ApiResponse<Vec<OrderInfoResponse>> = response
1556            .json()
1557            .await
1558            .map_err(|e| HttpError::InvalidResponse(e.to_string()))?;
1559
1560        if let Some(error) = api_response.error {
1561            return Err(HttpError::RequestFailed(format!(
1562                "API error: {} - {}",
1563                error.code, error.message
1564            )));
1565        }
1566
1567        api_response
1568            .result
1569            .ok_or_else(|| HttpError::InvalidResponse("No orders data in response".to_string()))
1570    }
1571
1572    /// Get open orders by label
1573    ///
1574    /// Retrieves open orders filtered by a specific label.
1575    ///
1576    /// # Arguments
1577    ///
1578    /// * `label` - The label to filter orders by
1579    /// * `currency` - The currency symbol (BTC, ETH, etc.)
1580    ///
1581    pub async fn get_open_orders_by_label(
1582        &self,
1583        label: &str,
1584        currency: &str,
1585    ) -> Result<Vec<OrderInfoResponse>, HttpError> {
1586        let url = format!(
1587            "{}{}?label={}&currency={}",
1588            self.base_url(),
1589            GET_OPEN_ORDERS_BY_LABEL,
1590            urlencoding::encode(label),
1591            urlencoding::encode(currency)
1592        );
1593
1594        let response = self.make_authenticated_request(&url).await?;
1595
1596        if !response.status().is_success() {
1597            let error_text = response
1598                .text()
1599                .await
1600                .unwrap_or_else(|_| "Unknown error".to_string());
1601            return Err(HttpError::RequestFailed(format!(
1602                "Get open orders by label failed: {}",
1603                error_text
1604            )));
1605        }
1606
1607        let api_response: ApiResponse<Vec<OrderInfoResponse>> = response
1608            .json()
1609            .await
1610            .map_err(|e| HttpError::InvalidResponse(e.to_string()))?;
1611
1612        if let Some(error) = api_response.error {
1613            return Err(HttpError::RequestFailed(format!(
1614                "API error: {} - {}",
1615                error.code, error.message
1616            )));
1617        }
1618
1619        api_response
1620            .result
1621            .ok_or_else(|| HttpError::InvalidResponse("No orders data in response".to_string()))
1622    }
1623
1624    /// Get order state
1625    ///
1626    /// Retrieves the state of a specific order.
1627    ///
1628    /// # Arguments
1629    ///
1630    /// * `order_id` - The order ID
1631    ///
1632    pub async fn get_order_state(&self, order_id: &str) -> Result<OrderInfoResponse, HttpError> {
1633        let url = format!(
1634            "{}{}?order_id={}",
1635            self.base_url(),
1636            GET_ORDER_STATE,
1637            urlencoding::encode(order_id)
1638        );
1639
1640        let response = self.make_authenticated_request(&url).await?;
1641
1642        if !response.status().is_success() {
1643            let error_text = response
1644                .text()
1645                .await
1646                .unwrap_or_else(|_| "Unknown error".to_string());
1647            return Err(HttpError::RequestFailed(format!(
1648                "Get order state failed: {}",
1649                error_text
1650            )));
1651        }
1652
1653        let api_response: ApiResponse<OrderInfoResponse> = response
1654            .json()
1655            .await
1656            .map_err(|e| HttpError::InvalidResponse(e.to_string()))?;
1657
1658        if let Some(error) = api_response.error {
1659            return Err(HttpError::RequestFailed(format!(
1660                "API error: {} - {}",
1661                error.code, error.message
1662            )));
1663        }
1664
1665        api_response
1666            .result
1667            .ok_or_else(|| HttpError::InvalidResponse("No order data in response".to_string()))
1668    }
1669
1670    /// Get open orders by currency
1671    ///
1672    /// Retrieves open orders for a specific currency.
1673    ///
1674    /// # Arguments
1675    ///
1676    /// * `currency` - The currency symbol (BTC, ETH, etc.)
1677    /// * `kind` - Instrument kind filter (optional)
1678    /// * `order_type` - Order type filter (optional)
1679    ///
1680    pub async fn get_open_orders_by_currency(
1681        &self,
1682        currency: &str,
1683        kind: Option<&str>,
1684        order_type: Option<&str>,
1685    ) -> Result<Vec<OrderInfoResponse>, HttpError> {
1686        let mut query_params = vec![("currency".to_string(), currency.to_string())];
1687
1688        if let Some(kind) = kind {
1689            query_params.push(("kind".to_string(), kind.to_string()));
1690        }
1691
1692        if let Some(order_type) = order_type {
1693            query_params.push(("type".to_string(), order_type.to_string()));
1694        }
1695
1696        let query_string = query_params
1697            .iter()
1698            .map(|(k, v)| format!("{}={}", k, urlencoding::encode(v)))
1699            .collect::<Vec<_>>()
1700            .join("&");
1701
1702        let url = format!(
1703            "{}{}?{}",
1704            self.base_url(),
1705            GET_OPEN_ORDERS_BY_CURRENCY,
1706            query_string
1707        );
1708
1709        let response = self.make_authenticated_request(&url).await?;
1710
1711        if !response.status().is_success() {
1712            let error_text = response
1713                .text()
1714                .await
1715                .unwrap_or_else(|_| "Unknown error".to_string());
1716            return Err(HttpError::RequestFailed(format!(
1717                "Get open orders by currency failed: {}",
1718                error_text
1719            )));
1720        }
1721
1722        let api_response: ApiResponse<Vec<OrderInfoResponse>> = response
1723            .json()
1724            .await
1725            .map_err(|e| HttpError::InvalidResponse(e.to_string()))?;
1726
1727        if let Some(error) = api_response.error {
1728            return Err(HttpError::RequestFailed(format!(
1729                "API error: {} - {}",
1730                error.code, error.message
1731            )));
1732        }
1733
1734        api_response
1735            .result
1736            .ok_or_else(|| HttpError::InvalidResponse("No orders data in response".to_string()))
1737    }
1738
1739    /// Get open orders by instrument
1740    ///
1741    /// Retrieves open orders for a specific instrument.
1742    ///
1743    /// # Arguments
1744    ///
1745    /// * `instrument_name` - The instrument name
1746    /// * `order_type` - Order type filter (optional)
1747    ///
1748    pub async fn get_open_orders_by_instrument(
1749        &self,
1750        instrument_name: &str,
1751        order_type: Option<&str>,
1752    ) -> Result<Vec<OrderInfoResponse>, HttpError> {
1753        let mut query_params = vec![("instrument_name".to_string(), instrument_name.to_string())];
1754
1755        if let Some(order_type) = order_type {
1756            query_params.push(("type".to_string(), order_type.to_string()));
1757        }
1758
1759        let query_string = query_params
1760            .iter()
1761            .map(|(k, v)| format!("{}={}", k, urlencoding::encode(v)))
1762            .collect::<Vec<_>>()
1763            .join("&");
1764
1765        let url = format!(
1766            "{}{}?{}",
1767            self.base_url(),
1768            GET_OPEN_ORDERS_BY_INSTRUMENT,
1769            query_string
1770        );
1771
1772        let response = self.make_authenticated_request(&url).await?;
1773
1774        if !response.status().is_success() {
1775            let error_text = response
1776                .text()
1777                .await
1778                .unwrap_or_else(|_| "Unknown error".to_string());
1779            return Err(HttpError::RequestFailed(format!(
1780                "Get open orders by instrument failed: {}",
1781                error_text
1782            )));
1783        }
1784
1785        let api_response: ApiResponse<Vec<OrderInfoResponse>> = response
1786            .json()
1787            .await
1788            .map_err(|e| HttpError::InvalidResponse(e.to_string()))?;
1789
1790        if let Some(error) = api_response.error {
1791            return Err(HttpError::RequestFailed(format!(
1792                "API error: {} - {}",
1793                error.code, error.message
1794            )));
1795        }
1796
1797        api_response
1798            .result
1799            .ok_or_else(|| HttpError::InvalidResponse("No orders data in response".to_string()))
1800    }
1801
1802    /// Get order history
1803    ///
1804    /// Retrieves history of orders that have been partially or fully filled.
1805    ///
1806    /// # Arguments
1807    ///
1808    /// * `currency` - Currency symbol (BTC, ETH, etc.)
1809    /// * `kind` - Instrument kind filter (optional)
1810    /// * `count` - Number of requested items (optional, default 20)
1811    /// * `offset` - Offset for pagination (optional)
1812    ///
1813    pub async fn get_order_history(
1814        &self,
1815        currency: &str,
1816        kind: Option<&str>,
1817        count: Option<u32>,
1818        offset: Option<u32>,
1819    ) -> Result<Vec<OrderInfoResponse>, HttpError> {
1820        let mut query_params = vec![("currency".to_string(), currency.to_string())];
1821
1822        if let Some(kind) = kind {
1823            query_params.push(("kind".to_string(), kind.to_string()));
1824        }
1825
1826        if let Some(count) = count {
1827            query_params.push(("count".to_string(), count.to_string()));
1828        }
1829
1830        if let Some(offset) = offset {
1831            query_params.push(("offset".to_string(), offset.to_string()));
1832        }
1833
1834        let query_string = query_params
1835            .iter()
1836            .map(|(k, v)| format!("{}={}", k, urlencoding::encode(v)))
1837            .collect::<Vec<_>>()
1838            .join("&");
1839
1840        let url = format!(
1841            "{}{}?{}",
1842            self.base_url(),
1843            GET_ORDER_HISTORY_BY_CURRENCY,
1844            query_string
1845        );
1846        let response = self.make_authenticated_request(&url).await?;
1847
1848        if !response.status().is_success() {
1849            let error_text = response
1850                .text()
1851                .await
1852                .unwrap_or_else(|_| "Unknown error".to_string());
1853            return Err(HttpError::RequestFailed(format!(
1854                "Get order history failed: {}",
1855                error_text
1856            )));
1857        }
1858
1859        let api_response: ApiResponse<Vec<OrderInfoResponse>> = response
1860            .json()
1861            .await
1862            .map_err(|e| HttpError::InvalidResponse(e.to_string()))?;
1863
1864        if let Some(error) = api_response.error {
1865            return Err(HttpError::RequestFailed(format!(
1866                "API error: {} - {}",
1867                error.code, error.message
1868            )));
1869        }
1870
1871        api_response.result.ok_or_else(|| {
1872            HttpError::InvalidResponse("No order history data in response".to_string())
1873        })
1874    }
1875
1876    /// Get order history by currency
1877    ///
1878    /// Retrieves order history for a specific currency.
1879    ///
1880    /// # Arguments
1881    ///
1882    /// * `currency` - Currency symbol (BTC, ETH, etc.)
1883    /// * `kind` - Instrument kind filter (optional)
1884    /// * `count` - Number of requested items (optional)
1885    /// * `offset` - Offset for pagination (optional)
1886    ///
1887    pub async fn get_order_history_by_currency(
1888        &self,
1889        currency: &str,
1890        kind: Option<&str>,
1891        count: Option<u32>,
1892        offset: Option<u32>,
1893    ) -> Result<Vec<OrderInfoResponse>, HttpError> {
1894        // This is an alias to the existing get_order_history method
1895        self.get_order_history(currency, kind, count, offset).await
1896    }
1897
1898    /// Get order history by instrument
1899    ///
1900    /// Retrieves order history for a specific instrument.
1901    ///
1902    /// # Arguments
1903    ///
1904    /// * `instrument_name` - The instrument name
1905    /// * `count` - Number of requested items (optional)
1906    /// * `offset` - Offset for pagination (optional)
1907    ///
1908    pub async fn get_order_history_by_instrument(
1909        &self,
1910        instrument_name: &str,
1911        count: Option<u32>,
1912        offset: Option<u32>,
1913    ) -> Result<Vec<OrderInfoResponse>, HttpError> {
1914        let mut query_params = vec![("instrument_name".to_string(), instrument_name.to_string())];
1915
1916        if let Some(count) = count {
1917            query_params.push(("count".to_string(), count.to_string()));
1918        }
1919
1920        if let Some(offset) = offset {
1921            query_params.push(("offset".to_string(), offset.to_string()));
1922        }
1923
1924        let query_string = query_params
1925            .iter()
1926            .map(|(k, v)| format!("{}={}", k, urlencoding::encode(v)))
1927            .collect::<Vec<_>>()
1928            .join("&");
1929
1930        let url = format!(
1931            "{}{}?{}",
1932            self.base_url(),
1933            GET_ORDER_HISTORY_BY_INSTRUMENT,
1934            query_string
1935        );
1936
1937        let response = self.make_authenticated_request(&url).await?;
1938
1939        if !response.status().is_success() {
1940            let error_text = response
1941                .text()
1942                .await
1943                .unwrap_or_else(|_| "Unknown error".to_string());
1944            return Err(HttpError::RequestFailed(format!(
1945                "Get order history by instrument failed: {}",
1946                error_text
1947            )));
1948        }
1949
1950        let api_response: ApiResponse<Vec<OrderInfoResponse>> = response
1951            .json()
1952            .await
1953            .map_err(|e| HttpError::InvalidResponse(e.to_string()))?;
1954
1955        if let Some(error) = api_response.error {
1956            return Err(HttpError::RequestFailed(format!(
1957                "API error: {} - {}",
1958                error.code, error.message
1959            )));
1960        }
1961
1962        api_response.result.ok_or_else(|| {
1963            HttpError::InvalidResponse("No order history data in response".to_string())
1964        })
1965    }
1966
1967    /// Get user trades by currency
1968    ///
1969    /// Retrieves user trades filtered by currency.
1970    ///
1971    /// # Arguments
1972    ///
1973    /// * `request` - A `TradesRequest` struct containing:
1974    ///   * `currency` - Currency symbol (BTC, ETH, etc.)
1975    ///   * `kind` - Instrument kind filter (optional)
1976    ///   * `start_id` - The ID of the first trade to be returned (optional)
1977    ///   * `end_id` - The ID of the last trade to be returned (optional)
1978    ///   * `count` - Number of requested items (optional, default 10, max 1000)
1979    ///   * `start_timestamp` - The earliest timestamp to return results from (optional)
1980    ///   * `end_timestamp` - The most recent timestamp to return results from (optional)
1981    ///   * `sorting` - Direction of results sorting (optional)
1982    ///   * `historical` - If true, retrieves historical records that persist indefinitely.
1983    ///     If false (default), retrieves recent records available for 24 hours.
1984    ///   * `subaccount_id` - The user id for the subaccount (optional)
1985    ///
1986    #[allow(clippy::too_many_arguments)]
1987    pub async fn get_user_trades_by_currency(
1988        &self,
1989        request: TradesRequest,
1990    ) -> Result<UserTradeWithPaginationResponse, HttpError> {
1991        let mut query_params = vec![("currency".to_string(), request.currency.to_string())];
1992
1993        if let Some(kind) = request.kind {
1994            query_params.push(("kind".to_string(), kind.to_string()));
1995        }
1996
1997        if let Some(start_id) = request.start_id {
1998            query_params.push(("start_id".to_string(), start_id));
1999        }
2000
2001        if let Some(end_id) = request.end_id {
2002            query_params.push(("end_id".to_string(), end_id));
2003        }
2004
2005        if let Some(count) = request.count {
2006            query_params.push(("count".to_string(), count.to_string()));
2007        }
2008
2009        if let Some(start_timestamp) = request.start_timestamp {
2010            query_params.push(("start_timestamp".to_string(), start_timestamp.to_string()));
2011        }
2012
2013        if let Some(end_timestamp) = request.end_timestamp {
2014            query_params.push(("end_timestamp".to_string(), end_timestamp.to_string()));
2015        }
2016
2017        if let Some(sorting) = request.sorting {
2018            query_params.push(("sorting".to_string(), sorting.to_string()));
2019        }
2020
2021        if let Some(historical) = request.historical {
2022            query_params.push(("historical".to_string(), historical.to_string()));
2023        }
2024
2025        if let Some(subaccount_id) = request.subaccount_id {
2026            query_params.push(("subaccount_id".to_string(), subaccount_id.to_string()));
2027        }
2028
2029        let query_string = query_params
2030            .iter()
2031            .map(|(k, v)| format!("{}={}", k, urlencoding::encode(v)))
2032            .collect::<Vec<_>>()
2033            .join("&");
2034
2035        let url = format!(
2036            "{}{}?{}",
2037            self.base_url(),
2038            GET_USER_TRADES_BY_CURRENCY,
2039            query_string
2040        );
2041
2042        let response = self.make_authenticated_request(&url).await?;
2043
2044        if !response.status().is_success() {
2045            let error_text = response
2046                .text()
2047                .await
2048                .unwrap_or_else(|_| "Unknown error".to_string());
2049            return Err(HttpError::RequestFailed(format!(
2050                "Get user trades by currency failed: {}",
2051                error_text
2052            )));
2053        }
2054
2055        // Debug: Log the raw response text before trying to parse it
2056        let response_text = response.text().await.map_err(|e| {
2057            HttpError::InvalidResponse(format!("Failed to read response text: {}", e))
2058        })?;
2059
2060        tracing::debug!(
2061            "Raw API response for get_user_trades_by_order: {}",
2062            response_text
2063        );
2064
2065        // Try to parse as JSON
2066        let api_response: ApiResponse<UserTradeWithPaginationResponse> =
2067            serde_json::from_str(&response_text).map_err(|e| {
2068                HttpError::InvalidResponse(format!(
2069                    "error decoding response body: {} - Raw response: {}",
2070                    e, response_text
2071                ))
2072            })?;
2073
2074        if let Some(error) = api_response.error {
2075            return Err(HttpError::RequestFailed(format!(
2076                "API error: {} - {}",
2077                error.code, error.message
2078            )));
2079        }
2080
2081        api_response.result.ok_or_else(|| {
2082            HttpError::InvalidResponse("No user trades data in response".to_string())
2083        })
2084    }
2085
2086    /// Get user trades by currency and time
2087    ///
2088    /// Retrieves user trades filtered by currency within a time range.
2089    ///
2090    /// # Arguments
2091    ///
2092    /// * `request` - A `TradesRequest` struct containing:
2093    ///   * `currency` - Currency symbol (BTC, ETH, etc.)
2094    ///   * `kind` - Instrument kind filter (optional)
2095    ///   * `start_id` - The ID of the first trade to be returned (optional)
2096    ///   * `end_id` - The ID of the last trade to be returned (optional)
2097    ///   * `count` - Number of requested items (optional, default 10, max 1000)
2098    ///   * `start_timestamp` - The earliest timestamp to return results from (optional)
2099    ///   * `end_timestamp` - The most recent timestamp to return results from (optional)
2100    ///   * `sorting` - Direction of results sorting (optional)
2101    ///   * `historical` - If true, retrieves historical records that persist indefinitely.
2102    ///     If false (default), retrieves recent records available for 24 hours.
2103    ///   * `subaccount_id` - The user id for the subaccount (optional)
2104    ///
2105    #[allow(clippy::too_many_arguments)]
2106    pub async fn get_user_trades_by_currency_and_time(
2107        &self,
2108        request: TradesRequest,
2109    ) -> Result<UserTradeWithPaginationResponse, HttpError> {
2110        let mut query_params = vec![("currency".to_string(), request.currency.to_string())];
2111
2112        if let Some(kind) = request.kind {
2113            query_params.push(("kind".to_string(), kind.to_string()));
2114        }
2115
2116        if let Some(start_id) = request.start_id {
2117            query_params.push(("start_id".to_string(), start_id));
2118        }
2119
2120        if let Some(end_id) = request.end_id {
2121            query_params.push(("end_id".to_string(), end_id));
2122        }
2123
2124        if let Some(count) = request.count {
2125            query_params.push(("count".to_string(), count.to_string()));
2126        }
2127
2128        if let Some(start_timestamp) = request.start_timestamp {
2129            query_params.push(("start_timestamp".to_string(), start_timestamp.to_string()));
2130        }
2131
2132        if let Some(end_timestamp) = request.end_timestamp {
2133            query_params.push(("end_timestamp".to_string(), end_timestamp.to_string()));
2134        }
2135
2136        if let Some(sorting) = request.sorting {
2137            query_params.push(("sorting".to_string(), sorting.to_string()));
2138        }
2139
2140        if let Some(historical) = request.historical {
2141            query_params.push(("historical".to_string(), historical.to_string()));
2142        }
2143
2144        if let Some(subaccount_id) = request.subaccount_id {
2145            query_params.push(("subaccount_id".to_string(), subaccount_id.to_string()));
2146        }
2147
2148        let query_string = query_params
2149            .iter()
2150            .map(|(k, v)| format!("{}={}", k, urlencoding::encode(v)))
2151            .collect::<Vec<_>>()
2152            .join("&");
2153
2154        let url = format!(
2155            "{}{}?{}",
2156            self.base_url(),
2157            GET_USER_TRADES_BY_CURRENCY_AND_TIME,
2158            query_string
2159        );
2160
2161        let response = self.make_authenticated_request(&url).await?;
2162
2163        if !response.status().is_success() {
2164            let error_text = response
2165                .text()
2166                .await
2167                .unwrap_or_else(|_| "Unknown error".to_string());
2168            return Err(HttpError::RequestFailed(format!(
2169                "Get user trades by currency and time failed: {}",
2170                error_text
2171            )));
2172        }
2173
2174        // Debug: Log the raw response text before trying to parse it
2175        let response_text = response.text().await.map_err(|e| {
2176            HttpError::InvalidResponse(format!("Failed to read response text: {}", e))
2177        })?;
2178
2179        tracing::debug!(
2180            "Raw API response for get_user_trades_by_order: {}",
2181            response_text
2182        );
2183
2184        // Try to parse as JSON
2185        let api_response: ApiResponse<UserTradeWithPaginationResponse> =
2186            serde_json::from_str(&response_text).map_err(|e| {
2187                HttpError::InvalidResponse(format!(
2188                    "error decoding response body: {} - Raw response: {}",
2189                    e, response_text
2190                ))
2191            })?;
2192
2193        if let Some(error) = api_response.error {
2194            return Err(HttpError::RequestFailed(format!(
2195                "API error: {} - {}",
2196                error.code, error.message
2197            )));
2198        }
2199
2200        api_response.result.ok_or_else(|| {
2201            HttpError::InvalidResponse("No user trades data in response".to_string())
2202        })
2203    }
2204
2205    /// Get user trades by instrument and time
2206    ///
2207    /// Retrieves user trades for a specific instrument within a time range.
2208    ///
2209    /// # Arguments
2210    ///
2211    /// * `instrument_name` - Instrument name
2212    /// * `start_timestamp` - Start timestamp in milliseconds
2213    /// * `end_timestamp` - End timestamp in milliseconds
2214    /// * `count` - Number of requested items (optional, default 10)
2215    /// * `include_old` - Include trades older than 7 days (optional)
2216    /// * `sorting` - Direction of results sorting (optional)
2217    ///
2218    pub async fn get_user_trades_by_instrument_and_time(
2219        &self,
2220        instrument_name: &str,
2221        start_timestamp: u64,
2222        end_timestamp: u64,
2223        count: Option<u32>,
2224        include_old: Option<bool>,
2225        sorting: Option<&str>,
2226    ) -> Result<UserTradeWithPaginationResponse, HttpError> {
2227        let mut query_params = vec![
2228            ("instrument_name".to_string(), instrument_name.to_string()),
2229            ("start_timestamp".to_string(), start_timestamp.to_string()),
2230            ("end_timestamp".to_string(), end_timestamp.to_string()),
2231        ];
2232
2233        if let Some(count) = count {
2234            query_params.push(("count".to_string(), count.to_string()));
2235        }
2236
2237        if let Some(include_old) = include_old {
2238            query_params.push(("include_old".to_string(), include_old.to_string()));
2239        }
2240
2241        if let Some(sorting) = sorting {
2242            query_params.push(("sorting".to_string(), sorting.to_string()));
2243        }
2244
2245        let query_string = query_params
2246            .iter()
2247            .map(|(k, v)| format!("{}={}", k, urlencoding::encode(v)))
2248            .collect::<Vec<_>>()
2249            .join("&");
2250
2251        let url = format!(
2252            "{}{}?{}",
2253            self.base_url(),
2254            GET_USER_TRADES_BY_INSTRUMENT_AND_TIME,
2255            query_string
2256        );
2257
2258        let response = self.make_authenticated_request(&url).await?;
2259
2260        if !response.status().is_success() {
2261            let error_text = response
2262                .text()
2263                .await
2264                .unwrap_or_else(|_| "Unknown error".to_string());
2265            return Err(HttpError::RequestFailed(format!(
2266                "Get user trades by instrument and time failed: {}",
2267                error_text
2268            )));
2269        }
2270
2271        // Debug: Log the raw response text before trying to parse it
2272        let response_text = response.text().await.map_err(|e| {
2273            HttpError::InvalidResponse(format!("Failed to read response text: {}", e))
2274        })?;
2275
2276        tracing::debug!(
2277            "Raw API response for get_user_trades_by_instrument_and_time: {}",
2278            response_text
2279        );
2280
2281        // Try to parse as JSON
2282        let api_response: ApiResponse<UserTradeWithPaginationResponse> =
2283            serde_json::from_str(&response_text).map_err(|e| {
2284                HttpError::InvalidResponse(format!(
2285                    "error decoding response body: {} - Raw response: {}",
2286                    e, response_text
2287                ))
2288            })?;
2289
2290        if let Some(error) = api_response.error {
2291            return Err(HttpError::RequestFailed(format!(
2292                "API error: {} - {}",
2293                error.code, error.message
2294            )));
2295        }
2296
2297        api_response.result.ok_or_else(|| {
2298            HttpError::InvalidResponse("No user trades data in response".to_string())
2299        })
2300    }
2301
2302    /// Get user trades by order
2303    ///
2304    /// Retrieves user trades for a specific order.
2305    ///
2306    /// # Arguments
2307    ///
2308    /// * `order_id` - Order ID
2309    /// * `sorting` - Direction of results sorting (optional)
2310    ///
2311    pub async fn get_user_trades_by_order(
2312        &self,
2313        order_id: &str,
2314        sorting: Option<&str>,
2315        historical: bool,
2316    ) -> Result<Vec<UserTradeResponseByOrder>, HttpError> {
2317        let mut query_params = vec![("order_id".to_string(), order_id.to_string())];
2318
2319        if let Some(sorting) = sorting {
2320            query_params.push(("sorting".to_string(), sorting.to_string()));
2321        }
2322        if historical {
2323            query_params.push(("historical".to_string(), historical.to_string()));
2324        }
2325
2326        let query_string = query_params
2327            .iter()
2328            .map(|(k, v)| format!("{}={}", k, urlencoding::encode(v)))
2329            .collect::<Vec<_>>()
2330            .join("&");
2331
2332        let url = format!(
2333            "{}{}?{}",
2334            self.base_url(),
2335            GET_USER_TRADES_BY_ORDER,
2336            query_string
2337        );
2338
2339        let response = self.make_authenticated_request(&url).await?;
2340
2341        if !response.status().is_success() {
2342            let error_text = response
2343                .text()
2344                .await
2345                .unwrap_or_else(|_| "Unknown error".to_string());
2346            return Err(HttpError::RequestFailed(format!(
2347                "Get user trades by order failed: {}",
2348                error_text
2349            )));
2350        }
2351
2352        let api_response: ApiResponse<Vec<UserTradeResponseByOrder>> = response
2353            .json()
2354            .await
2355            .map_err(|e| HttpError::InvalidResponse(e.to_string()))?;
2356
2357        if let Some(error) = api_response.error {
2358            return Err(HttpError::RequestFailed(format!(
2359                "API error: {} - {}",
2360                error.code, error.message
2361            )));
2362        }
2363
2364        api_response.result.ok_or_else(|| {
2365            HttpError::InvalidResponse("No user trades data in response".to_string())
2366        })
2367    }
2368}