deribit_http/endpoints/
public.rs

1//! REST API endpoints implementation
2//!
3//! This module implements all Deribit REST API endpoints including
4//! market data, trading, account management, and system endpoints.
5
6use crate::DeribitHttpClient;
7use crate::constants::endpoints::*;
8use crate::error::HttpError;
9use crate::model::LastTradesResponse;
10use crate::model::book::{BookSummary, OrderBook};
11use crate::model::currency::CurrencyStruct;
12use crate::model::funding::{FundingChartData, FundingRateData};
13use crate::model::index::{IndexData, IndexPriceData};
14use crate::model::instrument::{Instrument, OptionType};
15use crate::model::order::OrderSide;
16use crate::model::other::{OptionInstrument, OptionInstrumentPair};
17use crate::model::response::api_response::ApiResponse;
18use crate::model::response::other::{
19    AprHistoryResponse, ContractSizeResponse, DeliveryPricesResponse, ExpirationsResponse,
20    SettlementsResponse, StatusResponse, TestResponse,
21};
22use crate::model::ticker::TickerData;
23use crate::model::trade::{Liquidity, Trade};
24use crate::model::tradingview::TradingViewChartData;
25use std::collections::HashMap;
26
27/// Market data endpoints
28impl DeribitHttpClient {
29    /// Get all supported currencies
30    ///
31    /// Retrieves all cryptocurrencies supported by the API.
32    /// This is a public endpoint that doesn't require authentication.
33    ///
34    /// # Examples
35    ///
36    /// ```rust
37    /// # use deribit_http::DeribitHttpClient;
38    /// # #[tokio::main]
39    /// # async fn main() -> Result<(), Box<dyn std::error::Error>> {
40    /// let client = DeribitHttpClient::new(); // testnet
41    /// let currencies = client.get_currencies().await?;
42    /// for currency in currencies {
43    ///     println!("Currency: {} ({})", currency.currency, currency.currency_long);
44    /// }
45    /// # Ok(())
46    /// # }
47    /// ```
48    pub async fn get_currencies(&self) -> Result<Vec<CurrencyStruct>, HttpError> {
49        let url = format!("{}{}", self.base_url(), GET_CURRENCIES);
50
51        let response = self
52            .http_client()
53            .get(url)
54            .send()
55            .await
56            .map_err(|e| HttpError::NetworkError(e.to_string()))?;
57
58        if !response.status().is_success() {
59            let error_text = response
60                .text()
61                .await
62                .unwrap_or_else(|_| "Unknown error".to_string());
63            return Err(HttpError::RequestFailed(format!(
64                "Get currencies failed: {}",
65                error_text
66            )));
67        }
68
69        let api_response: ApiResponse<Vec<CurrencyStruct>> = response
70            .json()
71            .await
72            .map_err(|e| HttpError::InvalidResponse(e.to_string()))?;
73
74        if let Some(error) = api_response.error {
75            return Err(HttpError::RequestFailed(format!(
76                "API error: {} - {}",
77                error.code, error.message
78            )));
79        }
80
81        api_response
82            .result
83            .ok_or_else(|| HttpError::InvalidResponse("No currencies in response".to_string()))
84    }
85
86    /// Get current index price for a currency
87    ///
88    /// Retrieves the current index price for the instruments, for the selected currency.
89    /// This is a public endpoint that doesn't require authentication.
90    ///
91    /// # Arguments
92    ///
93    /// * `currency` - The currency symbol (BTC, ETH, USDC, USDT, EURR)
94    ///
95    pub async fn get_index(&self, currency: &str) -> Result<IndexData, HttpError> {
96        let url = format!("{}{}?currency={}", self.base_url(), GET_INDEX, currency);
97
98        let response = self
99            .http_client()
100            .get(&url)
101            .send()
102            .await
103            .map_err(|e| HttpError::NetworkError(e.to_string()))?;
104
105        if !response.status().is_success() {
106            let error_text = response
107                .text()
108                .await
109                .unwrap_or_else(|_| "Unknown error".to_string());
110            return Err(HttpError::RequestFailed(format!(
111                "Get index failed: {}",
112                error_text
113            )));
114        }
115
116        let api_response: ApiResponse<IndexData> = response
117            .json()
118            .await
119            .map_err(|e| HttpError::InvalidResponse(e.to_string()))?;
120
121        if let Some(error) = api_response.error {
122            return Err(HttpError::RequestFailed(format!(
123                "API error: {} - {}",
124                error.code, error.message
125            )));
126        }
127
128        api_response
129            .result
130            .ok_or_else(|| HttpError::InvalidResponse("No index data in response".to_string()))
131    }
132
133    /// Get index price by name
134    ///
135    /// Retrieves the current index price value for given index name.
136    /// This is a public endpoint that doesn't require authentication.
137    ///
138    /// # Arguments
139    ///
140    /// * `index_name` - The index identifier (e.g., "btc_usd", "eth_usd")
141    ///
142    /// # Examples
143    ///
144    /// ```rust
145    /// # use deribit_http::DeribitHttpClient;
146    /// # #[tokio::main]
147    /// # async fn main() -> Result<(), Box<dyn std::error::Error>> {
148    /// let client = DeribitHttpClient::new(); // testnet
149    /// let index_price = client.get_index_price("btc_usd").await?;
150    /// println!("Index price: {}", index_price.index_price);
151    /// # Ok(())
152    /// # }
153    /// ```
154    pub async fn get_index_price(&self, index_name: &str) -> Result<IndexPriceData, HttpError> {
155        let url = format!(
156            "{}{}?index_name={}",
157            self.base_url(),
158            GET_INDEX_PRICE,
159            index_name
160        );
161
162        let response = self
163            .http_client()
164            .get(&url)
165            .send()
166            .await
167            .map_err(|e| HttpError::NetworkError(e.to_string()))?;
168
169        if !response.status().is_success() {
170            let error_text = response
171                .text()
172                .await
173                .unwrap_or_else(|_| "Unknown error".to_string());
174            return Err(HttpError::RequestFailed(format!(
175                "Get index price failed: {}",
176                error_text
177            )));
178        }
179
180        let api_response: ApiResponse<IndexPriceData> = response
181            .json()
182            .await
183            .map_err(|e| HttpError::InvalidResponse(e.to_string()))?;
184
185        if let Some(error) = api_response.error {
186            return Err(HttpError::RequestFailed(format!(
187                "API error: {} - {}",
188                error.code, error.message
189            )));
190        }
191
192        api_response.result.ok_or_else(|| {
193            HttpError::InvalidResponse("No index price data in response".to_string())
194        })
195    }
196
197    /// Get all supported index price names
198    ///
199    /// Retrieves the identifiers of all supported Price Indexes.
200    /// This is a public endpoint that doesn't require authentication.
201    ///
202    /// # Examples
203    ///
204    /// ```rust
205    /// # use deribit_http::DeribitHttpClient;
206    /// # #[tokio::main]
207    /// # async fn main() -> Result<(), Box<dyn std::error::Error>> {
208    /// let client = DeribitHttpClient::new(); // testnet
209    /// let index_names = client.get_index_price_names().await?;
210    /// for name in index_names {
211    ///     println!("Index: {}", name);
212    /// }
213    /// # Ok(())
214    /// # }
215    /// ```
216    pub async fn get_index_price_names(&self) -> Result<Vec<String>, HttpError> {
217        let url = format!("{}{}", self.base_url(), GET_INDEX_PRICE_NAMES);
218
219        let response = self
220            .http_client()
221            .get(&url)
222            .send()
223            .await
224            .map_err(|e| HttpError::NetworkError(e.to_string()))?;
225
226        if !response.status().is_success() {
227            let error_text = response
228                .text()
229                .await
230                .unwrap_or_else(|_| "Unknown error".to_string());
231            return Err(HttpError::RequestFailed(format!(
232                "Get index price names failed: {}",
233                error_text
234            )));
235        }
236
237        let api_response: ApiResponse<Vec<String>> = response
238            .json()
239            .await
240            .map_err(|e| HttpError::InvalidResponse(e.to_string()))?;
241
242        if let Some(error) = api_response.error {
243            return Err(HttpError::RequestFailed(format!(
244                "API error: {} - {}",
245                error.code, error.message
246            )));
247        }
248
249        api_response.result.ok_or_else(|| {
250            HttpError::InvalidResponse("No index price names in response".to_string())
251        })
252    }
253
254    /// Get book summary by currency
255    ///
256    /// Retrieves the summary information such as open interest, 24h volume, etc.
257    /// for all instruments for the currency (optionally filtered by kind).
258    /// This is a public endpoint that doesn't require authentication.
259    ///
260    /// # Arguments
261    ///
262    /// * `currency` - The currency symbol (BTC, ETH, USDC, USDT, EURR)
263    /// * `kind` - Optional instrument kind filter (future, option, spot, future_combo, option_combo)
264    ///
265    /// # Examples
266    ///
267    /// ```rust
268    /// # use deribit_http::DeribitHttpClient;
269    /// # #[tokio::main]
270    /// # async fn main() -> Result<(), Box<dyn std::error::Error>> {
271    /// let client = DeribitHttpClient::new(); // testnet
272    /// let summaries = client.get_book_summary_by_currency("BTC", Some("future")).await?;
273    /// for summary in summaries {
274    ///     println!("Instrument: {} - Volume: {}", summary.instrument_name, summary.volume);
275    /// }
276    /// # Ok(())
277    /// # }
278    /// ```
279    pub async fn get_book_summary_by_currency(
280        &self,
281        currency: &str,
282        kind: Option<&str>,
283    ) -> Result<Vec<BookSummary>, HttpError> {
284        let mut url = format!(
285            "{}{}?currency={}",
286            self.base_url(),
287            GET_BOOK_SUMMARY_BY_CURRENCY,
288            currency
289        );
290
291        if let Some(kind) = kind {
292            url.push_str(&format!("&kind={}", kind));
293        }
294
295        let response = self
296            .http_client()
297            .get(&url)
298            .send()
299            .await
300            .map_err(|e| HttpError::NetworkError(e.to_string()))?;
301
302        if !response.status().is_success() {
303            let error_text = response
304                .text()
305                .await
306                .unwrap_or_else(|_| "Unknown error".to_string());
307            return Err(HttpError::RequestFailed(format!(
308                "Get book summary by currency failed: {}",
309                error_text
310            )));
311        }
312
313        let api_response: ApiResponse<Vec<BookSummary>> = response
314            .json()
315            .await
316            .map_err(|e| HttpError::InvalidResponse(e.to_string()))?;
317
318        if let Some(error) = api_response.error {
319            return Err(HttpError::RequestFailed(format!(
320                "API error: {} - {}",
321                error.code, error.message
322            )));
323        }
324
325        api_response.result.ok_or_else(|| {
326            HttpError::InvalidResponse("No book summary data in response".to_string())
327        })
328    }
329
330    /// Get single instrument information
331    ///
332    /// Retrieves detailed information about a specific instrument.
333    /// This is a public endpoint that doesn't require authentication.
334    ///
335    /// # Arguments
336    ///
337    /// * `instrument_name` - The instrument identifier (e.g., "BTC-PERPETUAL")
338    ///
339    /// # Examples
340    ///
341    /// ```rust
342    /// # use deribit_http::DeribitHttpClient;
343    /// # #[tokio::main]
344    /// # async fn main() -> Result<(), Box<dyn std::error::Error>> {
345    /// let client = DeribitHttpClient::new(); // testnet
346    /// let instrument = client.get_instrument("BTC-PERPETUAL").await?;
347    /// println!("Contract size: {:?}", instrument.contract_size);
348    /// # Ok(())
349    /// # }
350    /// ```
351    pub async fn get_instrument(&self, instrument_name: &str) -> Result<Instrument, HttpError> {
352        let url = format!(
353            "{}{}?instrument_name={}",
354            self.base_url(),
355            GET_INSTRUMENT,
356            instrument_name
357        );
358
359        let response = self
360            .http_client()
361            .get(&url)
362            .send()
363            .await
364            .map_err(|e| HttpError::NetworkError(e.to_string()))?;
365
366        if !response.status().is_success() {
367            let error_text = response
368                .text()
369                .await
370                .unwrap_or_else(|_| "Unknown error".to_string());
371            return Err(HttpError::RequestFailed(format!(
372                "Get instrument failed: {}",
373                error_text
374            )));
375        }
376
377        let api_response: ApiResponse<Instrument> = response
378            .json()
379            .await
380            .map_err(|e| HttpError::InvalidResponse(e.to_string()))?;
381
382        if let Some(error) = api_response.error {
383            return Err(HttpError::RequestFailed(format!(
384                "API error: {} - {}",
385                error.code, error.message
386            )));
387        }
388
389        api_response
390            .result
391            .ok_or_else(|| HttpError::InvalidResponse("No instrument data in response".to_string()))
392    }
393
394    /// Get book summary by instrument
395    ///
396    /// Retrieves the summary information such as open interest, 24h volume, etc.
397    /// for a specific instrument.
398    /// This is a public endpoint that doesn't require authentication.
399    ///
400    /// # Arguments
401    ///
402    /// * `instrument_name` - The instrument identifier (e.g., "BTC-PERPETUAL")
403    ///
404    pub async fn get_book_summary_by_instrument(
405        &self,
406        instrument_name: &str,
407    ) -> Result<BookSummary, HttpError> {
408        let url = format!(
409            "{}{}?instrument_name={}",
410            self.base_url(),
411            GET_BOOK_SUMMARY_BY_INSTRUMENT,
412            instrument_name
413        );
414
415        let response = self
416            .http_client()
417            .get(&url)
418            .send()
419            .await
420            .map_err(|e| HttpError::NetworkError(e.to_string()))?;
421
422        if !response.status().is_success() {
423            let error_text = response
424                .text()
425                .await
426                .unwrap_or_else(|_| "Unknown error".to_string());
427            return Err(HttpError::RequestFailed(format!(
428                "Get book summary by instrument failed: {}",
429                error_text
430            )));
431        }
432
433        // The API returns an array with one element, so we need to parse it as Vec<BookSummary>
434        let api_response: ApiResponse<Vec<BookSummary>> = response
435            .json()
436            .await
437            .map_err(|e| HttpError::InvalidResponse(e.to_string()))?;
438
439        if let Some(error) = api_response.error {
440            return Err(HttpError::RequestFailed(format!(
441                "API error: {} - {}",
442                error.code, error.message
443            )));
444        }
445
446        let book_summaries = api_response.result.ok_or_else(|| {
447            HttpError::InvalidResponse("No book summary data in response".to_string())
448        })?;
449
450        // Return the first (and typically only) element
451        book_summaries.into_iter().next().ok_or_else(|| {
452            HttpError::InvalidResponse("Empty book summary array in response".to_string())
453        })
454    }
455
456    /// Get contract size for an instrument
457    ///
458    /// Retrieves contract size for specified instrument.
459    /// This is a public endpoint that doesn't require authentication.
460    ///
461    /// # Arguments
462    ///
463    /// * `instrument_name` - The instrument identifier (e.g., "BTC-PERPETUAL")
464    ///
465    /// # Examples
466    ///
467    /// ```rust
468    /// # use deribit_http::DeribitHttpClient;
469    /// # #[tokio::main]
470    /// # async fn main() -> Result<(), Box<dyn std::error::Error>> {
471    /// let client = DeribitHttpClient::new(); // testnet
472    /// let contract_size = client.get_contract_size("BTC-PERPETUAL").await?;
473    /// println!("Contract size: {}", contract_size);
474    /// # Ok(())
475    /// # }
476    /// ```
477    pub async fn get_contract_size(&self, instrument_name: &str) -> Result<f64, HttpError> {
478        let url = format!(
479            "{}{}?instrument_name={}",
480            self.base_url(),
481            GET_CONTRACT_SIZE,
482            instrument_name
483        );
484
485        let response = self
486            .http_client()
487            .get(&url)
488            .send()
489            .await
490            .map_err(|e| HttpError::NetworkError(e.to_string()))?;
491
492        if !response.status().is_success() {
493            let error_text = response
494                .text()
495                .await
496                .unwrap_or_else(|_| "Unknown error".to_string());
497            return Err(HttpError::RequestFailed(format!(
498                "Get contract size failed: {}",
499                error_text
500            )));
501        }
502
503        // The API returns an object with contract_size field
504        let api_response: ApiResponse<ContractSizeResponse> = response
505            .json()
506            .await
507            .map_err(|e| HttpError::InvalidResponse(e.to_string()))?;
508
509        if let Some(error) = api_response.error {
510            return Err(HttpError::RequestFailed(format!(
511                "API error: {} - {}",
512                error.code, error.message
513            )));
514        }
515
516        let contract_size_response = api_response.result.ok_or_else(|| {
517            HttpError::InvalidResponse("No contract size in response".to_string())
518        })?;
519
520        Ok(contract_size_response.contract_size)
521    }
522
523    /// Get server time
524    ///
525    /// Returns the current server time in milliseconds since Unix epoch.
526    /// This is a public endpoint that doesn't require authentication.
527    ///
528    /// # Examples
529    ///
530    /// ```rust
531    /// # use deribit_http::DeribitHttpClient;
532    /// # #[tokio::main]
533    /// # async fn main() -> Result<(), Box<dyn std::error::Error>> {
534    /// let client = DeribitHttpClient::new(); // testnet
535    /// let server_time = client.get_server_time().await?;
536    /// println!("Server time: {}", server_time);
537    /// # Ok(())
538    /// # }
539    /// ```
540    pub async fn get_server_time(&self) -> Result<u64, HttpError> {
541        let url = format!("{}{}", self.base_url(), GET_SERVER_TIME);
542
543        let response = self
544            .http_client()
545            .get(&url)
546            .send()
547            .await
548            .map_err(|e| HttpError::NetworkError(e.to_string()))?;
549
550        if !response.status().is_success() {
551            let error_text = response
552                .text()
553                .await
554                .unwrap_or_else(|_| "Unknown error".to_string());
555            return Err(HttpError::RequestFailed(format!(
556                "Get server time failed: {}",
557                error_text
558            )));
559        }
560
561        let api_response: ApiResponse<u64> = response
562            .json()
563            .await
564            .map_err(|e| HttpError::InvalidResponse(e.to_string()))?;
565
566        if let Some(error) = api_response.error {
567            return Err(HttpError::RequestFailed(format!(
568                "API error: {} - {}",
569                error.code, error.message
570            )));
571        }
572
573        api_response
574            .result
575            .ok_or_else(|| HttpError::InvalidResponse("No server time in response".to_string()))
576    }
577
578    /// Test connectivity to the API
579    ///
580    /// Returns the API version to test connectivity.
581    /// This is a public endpoint that doesn't require authentication.
582    pub async fn test_connection(&self) -> Result<String, HttpError> {
583        let url = format!("{}{}", self.base_url(), TEST_CONNECTION);
584
585        let response = self
586            .http_client()
587            .get(&url)
588            .send()
589            .await
590            .map_err(|e| HttpError::NetworkError(e.to_string()))?;
591
592        if !response.status().is_success() {
593            let error_text = response
594                .text()
595                .await
596                .unwrap_or_else(|_| "Unknown error".to_string());
597            return Err(HttpError::RequestFailed(format!(
598                "Test connection failed: {}",
599                error_text
600            )));
601        }
602
603        let api_response: ApiResponse<TestResponse> = response
604            .json()
605            .await
606            .map_err(|e| HttpError::InvalidResponse(e.to_string()))?;
607
608        if let Some(error) = api_response.error {
609            return Err(HttpError::RequestFailed(format!(
610                "API error: {} - {}",
611                error.code, error.message
612            )));
613        }
614
615        let test_result = api_response
616            .result
617            .ok_or_else(|| HttpError::InvalidResponse("No test result in response".to_string()))?;
618
619        Ok(test_result.version)
620    }
621
622    /// Get platform status and locked currency indices
623    ///
624    /// Returns information about the platform status and any locked currency indices.
625    /// This is a public endpoint that doesn't require authentication.
626    ///
627    pub async fn get_status(&self) -> Result<StatusResponse, HttpError> {
628        let url = format!("{}{}", self.base_url(), GET_STATUS);
629
630        let response = self
631            .http_client()
632            .get(&url)
633            .send()
634            .await
635            .map_err(|e| HttpError::NetworkError(e.to_string()))?;
636
637        if !response.status().is_success() {
638            let error_text = response
639                .text()
640                .await
641                .unwrap_or_else(|_| "Unknown error".to_string());
642            return Err(HttpError::RequestFailed(format!(
643                "Get status failed: {}",
644                error_text
645            )));
646        }
647
648        // Try direct deserialization first (non-JSON-RPC response)
649        match response.json::<StatusResponse>().await {
650            Ok(status) => Ok(status),
651            Err(_) => {
652                // Fallback to JSON-RPC wrapper format
653                let response = self
654                    .http_client()
655                    .get(&url)
656                    .send()
657                    .await
658                    .map_err(|e| HttpError::NetworkError(e.to_string()))?;
659
660                let api_response: ApiResponse<StatusResponse> = response
661                    .json()
662                    .await
663                    .map_err(|e| HttpError::InvalidResponse(e.to_string()))?;
664
665                if let Some(error) = api_response.error {
666                    return Err(HttpError::RequestFailed(format!(
667                        "API error: {} - {}",
668                        error.code, error.message
669                    )));
670                }
671
672                api_response.result.ok_or_else(|| {
673                    HttpError::InvalidResponse("No status data in response".to_string())
674                })
675            }
676        }
677    }
678
679    /// Get APR history for yield tokens
680    ///
681    /// Retrieves historical APR data for specified currency. Only applicable to yield-generating tokens (USDE, STETH).
682    /// This is a public endpoint that doesn't require authentication.
683    ///
684    /// # Arguments
685    ///
686    /// * `currency` - Currency for which to retrieve APR history (usde or steth)
687    /// * `limit` - Optional number of days to retrieve (default 365, maximum 365)
688    /// * `before` - Optional parameter to receive APR history before given epoch day
689    ///
690    pub async fn get_apr_history(
691        &self,
692        currency: &str,
693        limit: Option<u32>,
694        before: Option<i32>,
695    ) -> Result<AprHistoryResponse, HttpError> {
696        let mut url = format!(
697            "{}{}?currency={}",
698            self.base_url(),
699            GET_APR_HISTORY,
700            currency
701        );
702
703        if let Some(limit) = limit {
704            url.push_str(&format!("&limit={}", limit));
705        }
706
707        if let Some(before) = before {
708            url.push_str(&format!("&before={}", before));
709        }
710
711        let response = self
712            .http_client()
713            .get(&url)
714            .send()
715            .await
716            .map_err(|e| HttpError::NetworkError(e.to_string()))?;
717
718        if !response.status().is_success() {
719            let error_text = response
720                .text()
721                .await
722                .unwrap_or_else(|_| "Unknown error".to_string());
723            return Err(HttpError::RequestFailed(format!(
724                "Get APR history failed: {}",
725                error_text
726            )));
727        }
728
729        let api_response: ApiResponse<AprHistoryResponse> = response
730            .json()
731            .await
732            .map_err(|e| HttpError::InvalidResponse(e.to_string()))?;
733
734        if let Some(error) = api_response.error {
735            return Err(HttpError::RequestFailed(format!(
736                "API error: {} - {}",
737                error.code, error.message
738            )));
739        }
740
741        api_response.result.ok_or_else(|| {
742            HttpError::InvalidResponse("No APR history data in response".to_string())
743        })
744    }
745
746    /// Get ticker information for an instrument
747    ///
748    /// Returns ticker data including last price, bid/ask, volume, etc.
749    ///
750    /// # Arguments
751    ///
752    /// * `instrument_name` - The instrument identifier (e.g., "BTC-PERPETUAL")
753    ///
754    /// # Examples
755    ///
756    /// ```rust
757    /// # use deribit_http::DeribitHttpClient;
758    /// # #[tokio::main]
759    /// # async fn main() -> Result<(), Box<dyn std::error::Error>> {
760    /// let client = DeribitHttpClient::new();
761    /// let ticker = client.get_ticker("BTC-PERPETUAL").await?;
762    /// println!("Last price: {:?}", ticker.last_price);
763    /// # Ok(())
764    /// # }
765    /// ```
766    pub async fn get_ticker(&self, instrument_name: &str) -> Result<TickerData, HttpError> {
767        let url = format!(
768            "{}{}?instrument_name={}",
769            self.base_url(),
770            GET_TICKER,
771            instrument_name
772        );
773
774        let response = self
775            .http_client()
776            .get(&url)
777            .send()
778            .await
779            .map_err(|e| HttpError::NetworkError(e.to_string()))?;
780
781        if !response.status().is_success() {
782            let error_text = response
783                .text()
784                .await
785                .unwrap_or_else(|_| "Unknown error".to_string());
786            return Err(HttpError::RequestFailed(format!(
787                "Get ticker failed: {}",
788                error_text
789            )));
790        }
791
792        let api_response: ApiResponse<TickerData> = response
793            .json()
794            .await
795            .map_err(|e| HttpError::InvalidResponse(e.to_string()))?;
796
797        if let Some(error) = api_response.error {
798            return Err(HttpError::RequestFailed(format!(
799                "API error: {} - {}",
800                error.code, error.message
801            )));
802        }
803
804        api_response
805            .result
806            .ok_or_else(|| HttpError::InvalidResponse("No ticker data in response".to_string()))
807    }
808
809    /// Get order book for an instrument
810    ///
811    /// Returns the current order book with bids and asks.
812    ///
813    /// # Arguments
814    ///
815    /// * `instrument_name` - The instrument identifier
816    /// * `depth` - Optional depth of the order book (default: 5)
817    pub async fn get_order_book(
818        &self,
819        instrument_name: &str,
820        depth: Option<u32>,
821    ) -> Result<OrderBook, HttpError> {
822        let mut url = format!(
823            "{}{}?instrument_name={}",
824            self.base_url(),
825            GET_ORDER_BOOK,
826            instrument_name
827        );
828
829        if let Some(d) = depth {
830            url.push_str(&format!("&depth={}", d));
831        }
832
833        let response = self
834            .http_client()
835            .get(&url)
836            .send()
837            .await
838            .map_err(|e| HttpError::NetworkError(e.to_string()))?;
839
840        if !response.status().is_success() {
841            let error_text = response
842                .text()
843                .await
844                .unwrap_or_else(|_| "Unknown error".to_string());
845            return Err(HttpError::RequestFailed(format!(
846                "Get order book failed: {}",
847                error_text
848            )));
849        }
850
851        let api_response: ApiResponse<OrderBook> = response
852            .json()
853            .await
854            .map_err(|e| HttpError::InvalidResponse(e.to_string()))?;
855
856        if let Some(error) = api_response.error {
857            return Err(HttpError::RequestFailed(format!(
858                "API error: {} - {}",
859                error.code, error.message
860            )));
861        }
862
863        api_response
864            .result
865            .ok_or_else(|| HttpError::InvalidResponse("No order book data in response".to_string()))
866    }
867
868    /// Retrieves a list of option instruments for a given currency and expiry date.
869    ///
870    /// This asynchronous function fetches option instruments for the specified `currency`
871    /// and `expiry` date and returns a filtered list of options along with their associated
872    /// ticker information.
873    ///
874    /// # Arguments
875    ///
876    /// * `currency` - A string slice that represents the name of the currency (e.g., "BTC", "ETH").
877    /// * `expiry` - A string slice representing the expiry date for the options (e.g., "20231027").
878    ///
879    /// # Returns
880    ///
881    /// Returns a `Result` containing a vector of `OptionInstrument` on success,
882    /// or an `HttpError` on failure.
883    ///
884    /// - On success, it returns a `Vec<OptionInstrument>`, where each option contains
885    ///   the instrument details and ticker information.
886    /// - On failure, it returns an `HttpError`, such as in cases where the instrument
887    ///   data could not be retrieved or tickers are inaccessible.
888    ///
889    /// # Errors
890    ///
891    /// This function may return an `HttpError` in the following scenarios:
892    ///
893    /// * If fetching the instrument data fails.
894    /// * If retrieving ticker information for an instrument fails.
895    ///
896    /// # Implementation Details
897    ///
898    /// 1. Fetches instruments for the specified `currency` filtered by type `option`.
899    /// 2. Filters the instruments to ensure they match the `currency`-`expiry` base name.
900    /// 3. Constructs an `OptionInstrument` for each filtered instrument, including
901    ///    the instrument details and ticker information.
902    ///
903    pub async fn get_options(
904        &self,
905        currency: &str,
906        expiry: &str,
907    ) -> Result<Vec<OptionInstrument>, HttpError> {
908        let mut instruments = self
909            .get_instruments(currency, Some("option"), Some(false))
910            .await
911            .map_err(|e| HttpError::RequestFailed(e.to_string()))?;
912
913        let base_name = format!("{}-{}", currency, expiry).to_uppercase();
914        // filter instruments by base name in instrument_name
915        instruments.retain(|i| i.instrument_name.starts_with(&base_name));
916
917        let mut options: Vec<OptionInstrument> = Vec::with_capacity(instruments.len());
918        for instrument in instruments {
919            let option = OptionInstrument {
920                instrument: instrument.clone(),
921                ticker: self.get_ticker(instrument.instrument_name.as_str()).await?,
922            };
923            options.push(option)
924        }
925        Ok(options)
926    }
927
928    /// Fetches option instruments for a given currency and expiry date, grouped by strike price.
929    ///
930    /// This method retrieves all option instruments for the specified currency and expiry,
931    /// then groups them into pairs (call and put) by strike price. Each strike price
932    /// maps to an `OptionInstrumentPair` containing the call and put options if available.
933    ///
934    /// # Arguments
935    ///
936    /// * `currency` - The currency symbol (e.g., "BTC", "ETH")
937    /// * `expiry` - The expiry date in format "DDMMMYY" (e.g., "10SEP25")
938    ///
939    /// # Returns
940    ///
941    /// Returns a `HashMap` where:
942    /// - Key: Strike price as `u64`
943    /// - Value: `OptionInstrumentPair` containing call and put options for that strike
944    ///
945    /// # Errors
946    ///
947    /// Returns `HttpError` if:
948    /// - The API request fails
949    /// - An option instrument has no option type
950    /// - Network or authentication errors occur
951    ///
952    /// # Example
953    ///
954    /// ```no_run
955    /// # use deribit_http::DeribitHttpClient;
956    /// # #[tokio::main]
957    /// # async fn main() -> Result<(), Box<dyn std::error::Error>> {
958    /// let client = DeribitHttpClient::new();
959    /// let pairs = client.get_options_pair("BTC", "10SEP25").await?;
960    ///
961    /// for (strike, pair) in pairs {
962    ///     println!("Strike {}: Call={:?}, Put={:?}",
963    ///              strike, pair.call.is_some(), pair.put.is_some());
964    /// }
965    /// # Ok(())
966    /// # }
967    /// ```
968    pub async fn get_options_pair(
969        &self,
970        currency: &str,
971        expiry: &str,
972    ) -> Result<HashMap<u64, OptionInstrumentPair>, HttpError> {
973        let option_instruments = self.get_options(currency, expiry).await?;
974
975        let mut strikes_map: HashMap<u64, OptionInstrumentPair> =
976            HashMap::with_capacity(option_instruments.len() / 2);
977        for instrument in option_instruments {
978            let strike_price = instrument.instrument.strike.unwrap() as u64;
979            strikes_map
980                .entry(strike_price)
981                .or_insert(OptionInstrumentPair {
982                    call: None,
983                    put: None,
984                });
985            match instrument.instrument.option_type.clone() {
986                Some(option_type) => match option_type {
987                    OptionType::Call => {
988                        strikes_map.get_mut(&strike_price).unwrap().call = Some(instrument.clone());
989                    }
990                    OptionType::Put => {
991                        strikes_map.get_mut(&strike_price).unwrap().put = Some(instrument.clone());
992                    }
993                },
994                None => {
995                    return Err(HttpError::RequestFailed(
996                        "Option instrument has no option type".to_string(),
997                    ));
998                }
999            }
1000        }
1001
1002        Ok(strikes_map)
1003    }
1004
1005    /// Get available instruments
1006    ///
1007    /// Returns a list of available trading instruments.
1008    ///
1009    /// # Arguments
1010    ///
1011    /// * `currency` - The currency (e.g., "BTC", "ETH")
1012    /// * `kind` - Optional instrument kind ("future", "option", "spot")
1013    /// * `expired` - Whether to include expired instruments
1014    pub async fn get_instruments(
1015        &self,
1016        currency: &str,
1017        kind: Option<&str>,
1018        expired: Option<bool>,
1019    ) -> Result<Vec<Instrument>, HttpError> {
1020        let mut url = format!(
1021            "{}{}?currency={}",
1022            self.base_url(),
1023            GET_INSTRUMENTS,
1024            currency
1025        );
1026
1027        if let Some(k) = kind {
1028            url.push_str(&format!("&kind={}", k));
1029        }
1030
1031        if let Some(exp) = expired {
1032            url.push_str(&format!("&expired={}", exp));
1033        }
1034
1035        let response = self
1036            .http_client()
1037            .get(&url)
1038            .send()
1039            .await
1040            .map_err(|e| HttpError::NetworkError(e.to_string()))?;
1041
1042        if !response.status().is_success() {
1043            let error_text = response
1044                .text()
1045                .await
1046                .unwrap_or_else(|_| "Unknown error".to_string());
1047            return Err(HttpError::RequestFailed(format!(
1048                "Get instruments failed: {}",
1049                error_text
1050            )));
1051        }
1052
1053        let api_response: ApiResponse<Vec<Instrument>> = response
1054            .json()
1055            .await
1056            .map_err(|e| HttpError::InvalidResponse(e.to_string()))?;
1057
1058        if let Some(error) = api_response.error {
1059            return Err(HttpError::RequestFailed(format!(
1060                "API error: {} - {}",
1061                error.code, error.message
1062            )));
1063        }
1064
1065        api_response.result.ok_or_else(|| {
1066            HttpError::InvalidResponse("No instruments data in response".to_string())
1067        })
1068    }
1069
1070    /// Get recent trades for an instrument
1071    ///
1072    /// Returns recent trade history for the specified instrument.
1073    ///
1074    /// # Arguments
1075    ///
1076    /// * `instrument_name` - The instrument identifier
1077    /// * `count` - Optional number of trades to return (default: 10, max: 1000)
1078    /// * `include_old` - Whether to include old trades
1079    pub async fn get_last_trades(
1080        &self,
1081        instrument_name: &str,
1082        count: Option<u32>,
1083        include_old: Option<bool>,
1084    ) -> Result<Vec<Trade>, HttpError> {
1085        let mut url = format!(
1086            "{}{}?instrument_name={}",
1087            self.base_url(),
1088            GET_LAST_TRADES_BY_INSTRUMENT,
1089            urlencoding::encode(instrument_name)
1090        );
1091
1092        if let Some(c) = count {
1093            url.push_str(&format!("&count={}", c));
1094        }
1095
1096        if let Some(old) = include_old {
1097            url.push_str(&format!("&include_old={}", old));
1098        }
1099
1100        let response = self
1101            .http_client()
1102            .get(&url)
1103            .send()
1104            .await
1105            .map_err(|e| HttpError::NetworkError(e.to_string()))?;
1106
1107        if !response.status().is_success() {
1108            let error_text = response
1109                .text()
1110                .await
1111                .unwrap_or_else(|_| "Unknown error".to_string());
1112            return Err(HttpError::RequestFailed(format!(
1113                "Get last trades failed: {}",
1114                error_text
1115            )));
1116        }
1117
1118        let api_response: ApiResponse<LastTradesResponse> = response
1119            .json()
1120            .await
1121            .map_err(|e| HttpError::InvalidResponse(e.to_string()))?;
1122
1123        if let Some(error) = api_response.error {
1124            return Err(HttpError::RequestFailed(format!(
1125                "API error: {} - {}",
1126                error.code, error.message
1127            )));
1128        }
1129
1130        let trades_response = api_response
1131            .result
1132            .ok_or_else(|| HttpError::InvalidResponse("No trades data in response".to_string()))?;
1133
1134        // Convert LastTrade to Trade
1135        let trades: Vec<Trade> = trades_response
1136            .trades
1137            .into_iter()
1138            .map(|last_trade| {
1139                Trade {
1140                    trade_id: last_trade.trade_id,
1141                    instrument_name: last_trade.instrument_name,
1142                    order_id: String::new(), // Not available in LastTrade
1143                    direction: match last_trade.direction.as_str() {
1144                        "buy" => OrderSide::Buy,
1145                        "sell" => OrderSide::Sell,
1146                        _ => OrderSide::Buy, // Default fallback
1147                    },
1148                    amount: last_trade.amount,
1149                    price: last_trade.price,
1150                    timestamp: last_trade.timestamp as i64,
1151                    fee: 0.0,                    // Not available in LastTrade
1152                    fee_currency: String::new(), // Not available in LastTrade
1153                    liquidity: Liquidity::Taker, // Default
1154                    mark_price: 0.0,             // Not available in LastTrade
1155                    index_price: last_trade.index_price,
1156                    instrument_kind: None, // Not available in LastTrade
1157                    trade_seq: Some(last_trade.trade_seq),
1158                    user_role: None,
1159                    block_trade: None,
1160                    underlying_price: None,
1161                    iv: last_trade.iv,
1162                    label: None,
1163                    profit_loss: None,
1164                    tick_direction: Some(last_trade.tick_direction),
1165                    self_trade: None,
1166                }
1167            })
1168            .collect();
1169
1170        Ok(trades)
1171    }
1172
1173    /// Get historical volatility
1174    ///
1175    /// Provides information about historical volatility for given cryptocurrency.
1176    ///
1177    /// # Arguments
1178    ///
1179    /// * `currency` - Currency symbol (BTC, ETH, etc.)
1180    ///
1181    /// # Examples
1182    ///
1183    /// ```rust
1184    /// use deribit_http::DeribitHttpClient;
1185    ///
1186    /// let client = DeribitHttpClient::new();
1187    /// // let volatility = client.get_historical_volatility("BTC").await?;
1188    /// // tracing::info!("Found {} volatility data points", volatility.len());
1189    /// ```
1190    pub async fn get_historical_volatility(
1191        &self,
1192        currency: &str,
1193    ) -> Result<Vec<[f64; 2]>, HttpError> {
1194        let url = format!(
1195            "{}{}?currency={}",
1196            self.base_url(),
1197            GET_HISTORICAL_VOLATILITY,
1198            urlencoding::encode(currency)
1199        );
1200
1201        let response = self
1202            .http_client()
1203            .get(&url)
1204            .send()
1205            .await
1206            .map_err(|e| HttpError::NetworkError(e.to_string()))?;
1207
1208        if !response.status().is_success() {
1209            let error_text = response
1210                .text()
1211                .await
1212                .unwrap_or_else(|_| "Unknown error".to_string());
1213            return Err(HttpError::RequestFailed(format!(
1214                "Get historical volatility failed: {}",
1215                error_text
1216            )));
1217        }
1218
1219        let api_response: ApiResponse<Vec<[f64; 2]>> = response
1220            .json()
1221            .await
1222            .map_err(|e| HttpError::InvalidResponse(e.to_string()))?;
1223
1224        if let Some(error) = api_response.error {
1225            return Err(HttpError::RequestFailed(format!(
1226                "API error: {} - {}",
1227                error.code, error.message
1228            )));
1229        }
1230
1231        api_response.result.ok_or_else(|| {
1232            HttpError::InvalidResponse("No historical volatility data in response".to_string())
1233        })
1234    }
1235
1236    /// Get funding chart data
1237    ///
1238    /// Retrieves the list of the latest PERPETUAL funding chart points within a given time period.
1239    ///
1240    /// # Arguments
1241    ///
1242    /// * `instrument_name` - Instrument name
1243    /// * `length` - Time period (8h, 24h, 1m)
1244    ///
1245    /// # Examples
1246    ///
1247    /// ```rust
1248    /// use deribit_http::DeribitHttpClient;
1249    ///
1250    /// let client = DeribitHttpClient::new();
1251    /// // let funding_data = client.get_funding_chart_data("BTC-PERPETUAL", "8h").await?;
1252    /// // tracing::info!("Current interest: {}", funding_data.current_interest);
1253    /// ```
1254    pub async fn get_funding_chart_data(
1255        &self,
1256        instrument_name: &str,
1257        length: &str,
1258    ) -> Result<FundingChartData, HttpError> {
1259        let url = format!(
1260            "{}{}?instrument_name={}&length={}",
1261            self.base_url(),
1262            GET_FUNDING_CHART_DATA,
1263            urlencoding::encode(instrument_name),
1264            urlencoding::encode(length)
1265        );
1266
1267        let response = self
1268            .http_client()
1269            .get(&url)
1270            .send()
1271            .await
1272            .map_err(|e| HttpError::NetworkError(e.to_string()))?;
1273
1274        if !response.status().is_success() {
1275            let error_text = response
1276                .text()
1277                .await
1278                .unwrap_or_else(|_| "Unknown error".to_string());
1279            return Err(HttpError::RequestFailed(format!(
1280                "Get funding chart data failed: {}",
1281                error_text
1282            )));
1283        }
1284
1285        let api_response: ApiResponse<FundingChartData> = response
1286            .json()
1287            .await
1288            .map_err(|e| HttpError::InvalidResponse(e.to_string()))?;
1289
1290        if let Some(error) = api_response.error {
1291            return Err(HttpError::RequestFailed(format!(
1292                "API error: {} - {}",
1293                error.code, error.message
1294            )));
1295        }
1296
1297        api_response.result.ok_or_else(|| {
1298            HttpError::InvalidResponse("No funding chart data in response".to_string())
1299        })
1300    }
1301
1302    /// Get TradingView chart data
1303    ///
1304    /// Publicly available market data used to generate a TradingView candle chart.
1305    ///
1306    /// # Arguments
1307    ///
1308    /// * `instrument_name` - Instrument name
1309    /// * `start_timestamp` - Start timestamp in milliseconds
1310    /// * `end_timestamp` - End timestamp in milliseconds
1311    /// * `resolution` - Chart resolution (1, 3, 5, 10, 15, 30, 60, 120, 180, 360)
1312    ///
1313    /// # Examples
1314    ///
1315    /// ```rust
1316    /// use deribit_http::DeribitHttpClient;
1317    ///
1318    /// let client = DeribitHttpClient::new();
1319    /// // let chart_data = client.get_tradingview_chart_data("BTC-PERPETUAL", 1554373800000, 1554376800000, "30").await?;
1320    /// // tracing::info!("Chart status: {}", chart_data.status);
1321    /// ```
1322    pub async fn get_tradingview_chart_data(
1323        &self,
1324        instrument_name: &str,
1325        start_timestamp: u64,
1326        end_timestamp: u64,
1327        resolution: &str,
1328    ) -> Result<TradingViewChartData, HttpError> {
1329        let url = format!(
1330            "{}{}?instrument_name={}&start_timestamp={}&end_timestamp={}&resolution={}",
1331            self.base_url(),
1332            GET_TRADINGVIEW_CHART_DATA,
1333            urlencoding::encode(instrument_name),
1334            start_timestamp,
1335            end_timestamp,
1336            urlencoding::encode(resolution)
1337        );
1338
1339        let response = self
1340            .http_client()
1341            .get(&url)
1342            .send()
1343            .await
1344            .map_err(|e| HttpError::NetworkError(e.to_string()))?;
1345
1346        if !response.status().is_success() {
1347            let error_text = response
1348                .text()
1349                .await
1350                .unwrap_or_else(|_| "Unknown error".to_string());
1351            return Err(HttpError::RequestFailed(format!(
1352                "Get TradingView chart data failed: {}",
1353                error_text
1354            )));
1355        }
1356
1357        let api_response: ApiResponse<TradingViewChartData> = response
1358            .json()
1359            .await
1360            .map_err(|e| HttpError::InvalidResponse(e.to_string()))?;
1361
1362        if let Some(error) = api_response.error {
1363            return Err(HttpError::RequestFailed(format!(
1364                "API error: {} - {}",
1365                error.code, error.message
1366            )));
1367        }
1368
1369        api_response.result.ok_or_else(|| {
1370            HttpError::InvalidResponse("No TradingView chart data in response".to_string())
1371        })
1372    }
1373
1374    /// Get delivery prices
1375    ///
1376    /// Retrieves delivery prices for the given index.
1377    /// This is a public endpoint that doesn't require authentication.
1378    ///
1379    /// # Arguments
1380    ///
1381    /// * `index_name` - Index identifier (e.g., "btc_usd", "eth_usd")
1382    /// * `count` - Number of requested items (optional, default 20)
1383    /// * `offset` - Offset for pagination (optional, default 0)
1384    ///
1385    /// # Examples
1386    ///
1387    /// ```rust
1388    /// # use deribit_http::DeribitHttpClient;
1389    /// # #[tokio::main]
1390    /// # async fn main() -> Result<(), Box<dyn std::error::Error>> {
1391    /// let client = DeribitHttpClient::new(); // testnet
1392    /// let delivery_prices = client.get_delivery_prices("btc_usd", Some(5), Some(0)).await?;
1393    /// for price in delivery_prices.data {
1394    ///     println!("Date: {} - Price: {}", price.date, price.delivery_price);
1395    /// }
1396    /// # Ok(())
1397    /// # }
1398    /// ```
1399    pub async fn get_delivery_prices(
1400        &self,
1401        index_name: &str,
1402        count: Option<u32>,
1403        offset: Option<u32>,
1404    ) -> Result<DeliveryPricesResponse, HttpError> {
1405        let mut url = format!(
1406            "{}{}?index_name={}",
1407            self.base_url(),
1408            GET_DELIVERY_PRICES,
1409            urlencoding::encode(index_name)
1410        );
1411
1412        if let Some(count) = count {
1413            url.push_str(&format!("&count={}", count));
1414        }
1415
1416        if let Some(offset) = offset {
1417            url.push_str(&format!("&offset={}", offset));
1418        }
1419
1420        let response = self
1421            .http_client()
1422            .get(&url)
1423            .send()
1424            .await
1425            .map_err(|e| HttpError::NetworkError(e.to_string()))?;
1426
1427        if !response.status().is_success() {
1428            let error_text = response
1429                .text()
1430                .await
1431                .unwrap_or_else(|_| "Unknown error".to_string());
1432            return Err(HttpError::RequestFailed(format!(
1433                "Get delivery prices failed: {}",
1434                error_text
1435            )));
1436        }
1437
1438        let api_response: ApiResponse<DeliveryPricesResponse> = response
1439            .json()
1440            .await
1441            .map_err(|e| HttpError::InvalidResponse(e.to_string()))?;
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 delivery prices data in response".to_string())
1452        })
1453    }
1454
1455    /// Get expirations
1456    ///
1457    /// Retrieves expirations for instruments. This method can be used to see instrument expirations.
1458    /// This is a public endpoint that doesn't require authentication.
1459    ///
1460    /// # Arguments
1461    ///
1462    /// * `currency` - The currency symbol (BTC, ETH, USDC, USDT, any, grouped)
1463    /// * `kind` - Instrument kind (future, option, any)
1464    /// * `currency_pair` - Currency pair identifier (optional)
1465    ///
1466    pub async fn get_expirations(
1467        &self,
1468        currency: &str,
1469        kind: &str,
1470        currency_pair: Option<&str>,
1471    ) -> Result<ExpirationsResponse, HttpError> {
1472        let mut url = format!(
1473            "{}{}?currency={}&kind={}",
1474            self.base_url(),
1475            GET_EXPIRATIONS,
1476            urlencoding::encode(currency),
1477            urlencoding::encode(kind)
1478        );
1479
1480        if let Some(currency_pair) = currency_pair {
1481            url.push_str(&format!(
1482                "&currency_pair={}",
1483                urlencoding::encode(currency_pair)
1484            ));
1485        }
1486
1487        let response = self
1488            .http_client()
1489            .get(&url)
1490            .send()
1491            .await
1492            .map_err(|e| HttpError::NetworkError(e.to_string()))?;
1493
1494        if !response.status().is_success() {
1495            let error_text = response
1496                .text()
1497                .await
1498                .unwrap_or_else(|_| "Unknown error".to_string());
1499            return Err(HttpError::RequestFailed(format!(
1500                "Get expirations failed: {}",
1501                error_text
1502            )));
1503        }
1504
1505        let api_response: ApiResponse<ExpirationsResponse> = response
1506            .json()
1507            .await
1508            .map_err(|e| HttpError::InvalidResponse(e.to_string()))?;
1509
1510        if let Some(error) = api_response.error {
1511            return Err(HttpError::RequestFailed(format!(
1512                "API error: {} - {}",
1513                error.code, error.message
1514            )));
1515        }
1516
1517        api_response.result.ok_or_else(|| {
1518            HttpError::InvalidResponse("No expirations data in response".to_string())
1519        })
1520    }
1521
1522    /// Get funding rate history
1523    ///
1524    /// Retrieves hourly historical interest rate for requested PERPETUAL instrument.
1525    /// This is a public endpoint that doesn't require authentication.
1526    ///
1527    /// # Arguments
1528    ///
1529    /// * `instrument_name` - Instrument name
1530    /// * `start_timestamp` - The earliest timestamp to return result from (milliseconds since UNIX epoch)
1531    /// * `end_timestamp` - The most recent timestamp to return result from (milliseconds since UNIX epoch)
1532    ///
1533    pub async fn get_funding_rate_history(
1534        &self,
1535        instrument_name: &str,
1536        start_timestamp: u64,
1537        end_timestamp: u64,
1538    ) -> Result<Vec<FundingRateData>, HttpError> {
1539        let url = format!(
1540            "{}{}?instrument_name={}&start_timestamp={}&end_timestamp={}",
1541            self.base_url(),
1542            GET_FUNDING_RATE_HISTORY,
1543            urlencoding::encode(instrument_name),
1544            start_timestamp,
1545            end_timestamp
1546        );
1547
1548        let response = self
1549            .http_client()
1550            .get(&url)
1551            .send()
1552            .await
1553            .map_err(|e| HttpError::NetworkError(e.to_string()))?;
1554
1555        if !response.status().is_success() {
1556            let error_text = response
1557                .text()
1558                .await
1559                .unwrap_or_else(|_| "Unknown error".to_string());
1560            return Err(HttpError::RequestFailed(format!(
1561                "Get funding rate history failed: {}",
1562                error_text
1563            )));
1564        }
1565
1566        let api_response: ApiResponse<Vec<FundingRateData>> = response
1567            .json()
1568            .await
1569            .map_err(|e| HttpError::InvalidResponse(e.to_string()))?;
1570
1571        if let Some(error) = api_response.error {
1572            return Err(HttpError::RequestFailed(format!(
1573                "API error: {} - {}",
1574                error.code, error.message
1575            )));
1576        }
1577
1578        api_response.result.ok_or_else(|| {
1579            HttpError::InvalidResponse("No funding rate history data in response".to_string())
1580        })
1581    }
1582
1583    /// Get funding rate value
1584    ///
1585    /// Retrieves interest rate value for requested period. Applicable only for PERPETUAL instruments.
1586    /// This is a public endpoint that doesn't require authentication.
1587    ///
1588    /// # Arguments
1589    ///
1590    /// * `instrument_name` - Instrument name
1591    /// * `start_timestamp` - The earliest timestamp to return result from (milliseconds since UNIX epoch)
1592    /// * `end_timestamp` - The most recent timestamp to return result from (milliseconds since UNIX epoch)
1593    ///
1594    /// # Examples
1595    ///
1596    /// ```rust
1597    /// # use deribit_http::DeribitHttpClient;
1598    /// # #[tokio::main]
1599    /// # async fn main() -> Result<(), Box<dyn std::error::Error>> {
1600    /// let client = DeribitHttpClient::new(); // testnet
1601    /// let funding_rate = client.get_funding_rate_value("BTC-PERPETUAL", 1569888000000, 1569974400000).await?;
1602    /// println!("Funding rate for period: {}", funding_rate);
1603    /// # Ok(())
1604    /// # }
1605    /// ```
1606    pub async fn get_funding_rate_value(
1607        &self,
1608        instrument_name: &str,
1609        start_timestamp: u64,
1610        end_timestamp: u64,
1611    ) -> Result<f64, HttpError> {
1612        let url = format!(
1613            "{}{}?instrument_name={}&start_timestamp={}&end_timestamp={}",
1614            self.base_url(),
1615            GET_FUNDING_RATE_VALUE,
1616            urlencoding::encode(instrument_name),
1617            start_timestamp,
1618            end_timestamp
1619        );
1620
1621        let response = self
1622            .http_client()
1623            .get(&url)
1624            .send()
1625            .await
1626            .map_err(|e| HttpError::NetworkError(e.to_string()))?;
1627
1628        if !response.status().is_success() {
1629            let error_text = response
1630                .text()
1631                .await
1632                .unwrap_or_else(|_| "Unknown error".to_string());
1633            return Err(HttpError::RequestFailed(format!(
1634                "Get funding rate value failed: {}",
1635                error_text
1636            )));
1637        }
1638
1639        let api_response: ApiResponse<f64> = response
1640            .json()
1641            .await
1642            .map_err(|e| HttpError::InvalidResponse(e.to_string()))?;
1643
1644        if let Some(error) = api_response.error {
1645            return Err(HttpError::RequestFailed(format!(
1646                "API error: {} - {}",
1647                error.code, error.message
1648            )));
1649        }
1650
1651        api_response.result.ok_or_else(|| {
1652            HttpError::InvalidResponse("No funding rate value in response".to_string())
1653        })
1654    }
1655
1656    /// Get last settlements by currency
1657    ///
1658    /// Retrieves historical settlement, delivery and bankruptcy events coming from all instruments within a given currency.
1659    /// This is a public endpoint that doesn't require authentication.
1660    ///
1661    /// # Arguments
1662    ///
1663    /// * `currency` - The currency symbol (BTC, ETH, USDC, USDT, EURR)
1664    /// * `settlement_type` - Settlement type (settlement, delivery, bankruptcy) - optional
1665    /// * `count` - Number of requested items (optional, default 20)
1666    /// * `continuation` - Continuation token for pagination (optional)
1667    /// * `search_start_timestamp` - The latest timestamp to return result from (optional)
1668    ///
1669    pub async fn get_last_settlements_by_currency(
1670        &self,
1671        currency: &str,
1672        settlement_type: Option<&str>,
1673        count: Option<u32>,
1674        continuation: Option<&str>,
1675        search_start_timestamp: Option<u64>,
1676    ) -> Result<SettlementsResponse, HttpError> {
1677        let mut url = format!(
1678            "{}{}?currency={}",
1679            self.base_url(),
1680            GET_LAST_SETTLEMENTS_BY_CURRENCY,
1681            urlencoding::encode(currency)
1682        );
1683
1684        if let Some(settlement_type) = settlement_type {
1685            url.push_str(&format!("&type={}", urlencoding::encode(settlement_type)));
1686        }
1687
1688        if let Some(count) = count {
1689            url.push_str(&format!("&count={}", count));
1690        }
1691
1692        if let Some(continuation) = continuation {
1693            url.push_str(&format!(
1694                "&continuation={}",
1695                urlencoding::encode(continuation)
1696            ));
1697        }
1698
1699        if let Some(search_start_timestamp) = search_start_timestamp {
1700            url.push_str(&format!(
1701                "&search_start_timestamp={}",
1702                search_start_timestamp
1703            ));
1704        }
1705
1706        let response = self
1707            .http_client()
1708            .get(&url)
1709            .send()
1710            .await
1711            .map_err(|e| HttpError::NetworkError(e.to_string()))?;
1712
1713        if !response.status().is_success() {
1714            let error_text = response
1715                .text()
1716                .await
1717                .unwrap_or_else(|_| "Unknown error".to_string());
1718            return Err(HttpError::RequestFailed(format!(
1719                "Get last settlements by currency failed: {}",
1720                error_text
1721            )));
1722        }
1723
1724        let api_response: ApiResponse<SettlementsResponse> = response
1725            .json()
1726            .await
1727            .map_err(|e| HttpError::InvalidResponse(e.to_string()))?;
1728
1729        if let Some(error) = api_response.error {
1730            return Err(HttpError::RequestFailed(format!(
1731                "API error: {} - {}",
1732                error.code, error.message
1733            )));
1734        }
1735
1736        api_response.result.ok_or_else(|| {
1737            HttpError::InvalidResponse("No settlements data in response".to_string())
1738        })
1739    }
1740
1741    /// Get last settlements by instrument
1742    ///
1743    /// Retrieves historical public settlement, delivery and bankruptcy events filtered by instrument name.
1744    /// This is a public endpoint that doesn't require authentication.
1745    ///
1746    /// # Arguments
1747    ///
1748    /// * `instrument_name` - Instrument name
1749    /// * `settlement_type` - Settlement type (settlement, delivery, bankruptcy) - optional
1750    /// * `count` - Number of requested items (optional, default 20)
1751    /// * `continuation` - Continuation token for pagination (optional)
1752    /// * `search_start_timestamp` - The latest timestamp to return result from (optional)
1753    ///
1754    pub async fn get_last_settlements_by_instrument(
1755        &self,
1756        instrument_name: &str,
1757        settlement_type: Option<&str>,
1758        count: Option<u32>,
1759        continuation: Option<&str>,
1760        search_start_timestamp: Option<u64>,
1761    ) -> Result<SettlementsResponse, HttpError> {
1762        let mut url = format!(
1763            "{}{}?instrument_name={}",
1764            self.base_url(),
1765            GET_LAST_SETTLEMENTS_BY_INSTRUMENT,
1766            urlencoding::encode(instrument_name)
1767        );
1768
1769        if let Some(settlement_type) = settlement_type {
1770            url.push_str(&format!("&type={}", urlencoding::encode(settlement_type)));
1771        }
1772
1773        if let Some(count) = count {
1774            url.push_str(&format!("&count={}", count));
1775        }
1776
1777        if let Some(continuation) = continuation {
1778            url.push_str(&format!(
1779                "&continuation={}",
1780                urlencoding::encode(continuation)
1781            ));
1782        }
1783
1784        if let Some(search_start_timestamp) = search_start_timestamp {
1785            url.push_str(&format!(
1786                "&search_start_timestamp={}",
1787                search_start_timestamp
1788            ));
1789        }
1790
1791        let response = self
1792            .http_client()
1793            .get(&url)
1794            .send()
1795            .await
1796            .map_err(|e| HttpError::NetworkError(e.to_string()))?;
1797
1798        if !response.status().is_success() {
1799            let error_text = response
1800                .text()
1801                .await
1802                .unwrap_or_else(|_| "Unknown error".to_string());
1803            return Err(HttpError::RequestFailed(format!(
1804                "Get last settlements by instrument failed: {}",
1805                error_text
1806            )));
1807        }
1808
1809        let api_response: ApiResponse<SettlementsResponse> = response
1810            .json()
1811            .await
1812            .map_err(|e| HttpError::InvalidResponse(e.to_string()))?;
1813
1814        if let Some(error) = api_response.error {
1815            return Err(HttpError::RequestFailed(format!(
1816                "API error: {} - {}",
1817                error.code, error.message
1818            )));
1819        }
1820
1821        api_response.result.ok_or_else(|| {
1822            HttpError::InvalidResponse("No settlements data in response".to_string())
1823        })
1824    }
1825
1826    /// Get last trades by currency
1827    ///
1828    /// Retrieves the latest trades that have occurred for instruments in a specific currency.
1829    /// This is a public endpoint that doesn't require authentication.
1830    ///
1831    /// # Arguments
1832    ///
1833    /// * `currency` - The currency symbol (BTC, ETH, USDC, USDT, EURR)
1834    /// * `kind` - Instrument kind (future, option, spot, etc.) - optional
1835    /// * `count` - Number of requested items (optional, default 10)
1836    /// * `include_old` - Include trades older than 7 days (optional)
1837    /// * `sorting` - Direction of results sorting (optional)
1838    ///
1839    /// # Examples
1840    ///
1841    /// ```rust
1842    /// # use deribit_http::DeribitHttpClient;
1843    /// # #[tokio::main]
1844    /// # async fn main() -> Result<(), Box<dyn std::error::Error>> {
1845    /// let client = DeribitHttpClient::new(); // testnet
1846    /// let trades = client.get_last_trades_by_currency("BTC", Some("future"), Some(10), Some(false), Some("desc")).await?;
1847    /// for trade in trades.trades {
1848    ///     println!("Trade: {} {} at {}", trade.amount, trade.instrument_name, trade.price);
1849    /// }
1850    /// # Ok(())
1851    /// # }
1852    /// ```
1853    pub async fn get_last_trades_by_currency(
1854        &self,
1855        currency: &str,
1856        kind: Option<&str>,
1857        count: Option<u32>,
1858        include_old: Option<bool>,
1859        sorting: Option<&str>,
1860    ) -> Result<LastTradesResponse, HttpError> {
1861        let mut url = format!(
1862            "{}{}?currency={}",
1863            self.base_url(),
1864            GET_LAST_TRADES_BY_CURRENCY,
1865            urlencoding::encode(currency)
1866        );
1867
1868        if let Some(kind) = kind {
1869            url.push_str(&format!("&kind={}", urlencoding::encode(kind)));
1870        }
1871
1872        if let Some(count) = count {
1873            url.push_str(&format!("&count={}", count));
1874        }
1875
1876        if let Some(include_old) = include_old {
1877            url.push_str(&format!("&include_old={}", include_old));
1878        }
1879
1880        if let Some(sorting) = sorting {
1881            url.push_str(&format!("&sorting={}", urlencoding::encode(sorting)));
1882        }
1883
1884        let response = self
1885            .http_client()
1886            .get(&url)
1887            .send()
1888            .await
1889            .map_err(|e| HttpError::NetworkError(e.to_string()))?;
1890
1891        if !response.status().is_success() {
1892            let error_text = response
1893                .text()
1894                .await
1895                .unwrap_or_else(|_| "Unknown error".to_string());
1896            return Err(HttpError::RequestFailed(format!(
1897                "Get last trades by currency failed: {}",
1898                error_text
1899            )));
1900        }
1901
1902        let api_response: ApiResponse<LastTradesResponse> = response
1903            .json()
1904            .await
1905            .map_err(|e| HttpError::InvalidResponse(e.to_string()))?;
1906
1907        if let Some(error) = api_response.error {
1908            return Err(HttpError::RequestFailed(format!(
1909                "API error: {} - {}",
1910                error.code, error.message
1911            )));
1912        }
1913
1914        api_response
1915            .result
1916            .ok_or_else(|| HttpError::InvalidResponse("No trades data in response".to_string()))
1917    }
1918
1919    /// Get last trades by currency and time
1920    ///
1921    /// Retrieves the latest trades that have occurred for instruments in a specific currency within a time range.
1922    /// This is a public endpoint that doesn't require authentication.
1923    ///
1924    /// # Arguments
1925    ///
1926    /// * `currency` - The currency symbol (BTC, ETH, USDC, USDT, EURR)
1927    /// * `start_timestamp` - The earliest timestamp to return result from (milliseconds since UNIX epoch)
1928    /// * `end_timestamp` - The most recent timestamp to return result from (milliseconds since UNIX epoch)
1929    /// * `kind` - Instrument kind (future, option, spot, etc.) - optional
1930    /// * `count` - Number of requested items (optional, default 10)
1931    /// * `include_old` - Include trades older than 7 days (optional)
1932    /// * `sorting` - Direction of results sorting (optional)
1933    ///
1934    /// # Examples
1935    ///
1936    /// ```rust
1937    /// # use deribit_http::DeribitHttpClient;
1938    /// # #[tokio::main]
1939    /// # async fn main() -> Result<(), Box<dyn std::error::Error>> {
1940    /// let client = DeribitHttpClient::new(); // testnet
1941    /// let trades = client.get_last_trades_by_currency_and_time("BTC", 1569888000000, 1569974400000, Some("future"), Some(10), Some(false), Some("desc")).await?;
1942    /// for trade in trades.trades {
1943    ///     println!("Trade: {} {} at {}", trade.amount, trade.instrument_name, trade.price);
1944    /// }
1945    /// # Ok(())
1946    /// # }
1947    /// ```
1948    #[allow(clippy::too_many_arguments)]
1949    pub async fn get_last_trades_by_currency_and_time(
1950        &self,
1951        currency: &str,
1952        start_timestamp: u64,
1953        end_timestamp: u64,
1954        kind: Option<&str>,
1955        count: Option<u32>,
1956        include_old: Option<bool>,
1957        sorting: Option<&str>,
1958    ) -> Result<LastTradesResponse, HttpError> {
1959        let mut url = format!(
1960            "{}{}?currency={}&start_timestamp={}&end_timestamp={}",
1961            self.base_url(),
1962            GET_LAST_TRADES_BY_CURRENCY_AND_TIME,
1963            urlencoding::encode(currency),
1964            start_timestamp,
1965            end_timestamp
1966        );
1967
1968        if let Some(kind) = kind {
1969            url.push_str(&format!("&kind={}", urlencoding::encode(kind)));
1970        }
1971
1972        if let Some(count) = count {
1973            url.push_str(&format!("&count={}", count));
1974        }
1975
1976        if let Some(include_old) = include_old {
1977            url.push_str(&format!("&include_old={}", include_old));
1978        }
1979
1980        if let Some(sorting) = sorting {
1981            url.push_str(&format!("&sorting={}", urlencoding::encode(sorting)));
1982        }
1983
1984        let response = self
1985            .http_client()
1986            .get(&url)
1987            .send()
1988            .await
1989            .map_err(|e| HttpError::NetworkError(e.to_string()))?;
1990
1991        if !response.status().is_success() {
1992            let error_text = response
1993                .text()
1994                .await
1995                .unwrap_or_else(|_| "Unknown error".to_string());
1996            return Err(HttpError::RequestFailed(format!(
1997                "Get last trades by currency and time failed: {}",
1998                error_text
1999            )));
2000        }
2001
2002        let api_response: ApiResponse<LastTradesResponse> = response
2003            .json()
2004            .await
2005            .map_err(|e| HttpError::InvalidResponse(e.to_string()))?;
2006
2007        if let Some(error) = api_response.error {
2008            return Err(HttpError::RequestFailed(format!(
2009                "API error: {} - {}",
2010                error.code, error.message
2011            )));
2012        }
2013
2014        api_response
2015            .result
2016            .ok_or_else(|| HttpError::InvalidResponse("No trades data in response".to_string()))
2017    }
2018
2019    /// Get last trades by instrument and time
2020    ///
2021    /// Retrieves the latest trades that have occurred for a specific instrument within a time range.
2022    /// This is a public endpoint that doesn't require authentication.
2023    ///
2024    /// # Arguments
2025    ///
2026    /// * `instrument_name` - Instrument name
2027    /// * `start_timestamp` - The earliest timestamp to return result from (milliseconds since UNIX epoch)
2028    /// * `end_timestamp` - The most recent timestamp to return result from (milliseconds since UNIX epoch)
2029    /// * `count` - Number of requested items (optional, default 10)
2030    /// * `include_old` - Include trades older than 7 days (optional)
2031    /// * `sorting` - Direction of results sorting (optional)
2032    ///
2033    /// # Examples
2034    ///
2035    /// ```rust
2036    /// # use deribit_http::DeribitHttpClient;
2037    /// # #[tokio::main]
2038    /// # async fn main() -> Result<(), Box<dyn std::error::Error>> {
2039    /// let client = DeribitHttpClient::new(); // testnet
2040    /// let trades = client.get_last_trades_by_instrument_and_time("BTC-PERPETUAL", 1569888000000, 1569974400000, Some(10), Some(false), Some("desc")).await?;
2041    /// for trade in trades.trades {
2042    ///     println!("Trade: {} at {} ({})", trade.amount, trade.price, trade.direction);
2043    /// }
2044    /// # Ok(())
2045    /// # }
2046    /// ```
2047    pub async fn get_last_trades_by_instrument_and_time(
2048        &self,
2049        instrument_name: &str,
2050        start_timestamp: u64,
2051        end_timestamp: u64,
2052        count: Option<u32>,
2053        include_old: Option<bool>,
2054        sorting: Option<&str>,
2055    ) -> Result<LastTradesResponse, HttpError> {
2056        let mut url = format!(
2057            "{}{}?instrument_name={}&start_timestamp={}&end_timestamp={}",
2058            self.base_url(),
2059            GET_LAST_TRADES_BY_INSTRUMENT_AND_TIME,
2060            urlencoding::encode(instrument_name),
2061            start_timestamp,
2062            end_timestamp
2063        );
2064
2065        if let Some(count) = count {
2066            url.push_str(&format!("&count={}", count));
2067        }
2068
2069        if let Some(include_old) = include_old {
2070            url.push_str(&format!("&include_old={}", include_old));
2071        }
2072
2073        if let Some(sorting) = sorting {
2074            url.push_str(&format!("&sorting={}", urlencoding::encode(sorting)));
2075        }
2076
2077        let response = self
2078            .http_client()
2079            .get(&url)
2080            .send()
2081            .await
2082            .map_err(|e| HttpError::NetworkError(e.to_string()))?;
2083
2084        if !response.status().is_success() {
2085            let error_text = response
2086                .text()
2087                .await
2088                .unwrap_or_else(|_| "Unknown error".to_string());
2089            return Err(HttpError::RequestFailed(format!(
2090                "Get last trades by instrument and time failed: {}",
2091                error_text
2092            )));
2093        }
2094
2095        let api_response: ApiResponse<LastTradesResponse> = response
2096            .json()
2097            .await
2098            .map_err(|e| HttpError::InvalidResponse(e.to_string()))?;
2099
2100        if let Some(error) = api_response.error {
2101            return Err(HttpError::RequestFailed(format!(
2102                "API error: {} - {}",
2103                error.code, error.message
2104            )));
2105        }
2106
2107        api_response
2108            .result
2109            .ok_or_else(|| HttpError::InvalidResponse("No trades data in response".to_string()))
2110    }
2111
2112    /// Get order book by instrument ID
2113    ///
2114    /// Retrieves the order book for the specified instrument by its ID.
2115    /// This is a public endpoint that doesn't require authentication.
2116    ///
2117    /// # Arguments
2118    ///
2119    /// * `instrument_id` - Instrument ID
2120    /// * `depth` - The number of entries to return for bid and ask order book entries (optional)
2121    ///
2122    /// # Examples
2123    ///
2124    /// ```rust,no_run
2125    /// # use deribit_http::DeribitHttpClient;
2126    /// # #[tokio::main]
2127    /// # async fn main() -> Result<(), Box<dyn std::error::Error>> {
2128    /// let client = DeribitHttpClient::new(); // testnet
2129    /// let order_book = client.get_order_book_by_instrument_id(42, Some(5)).await?;
2130    /// println!("Order book for {}: {} bids, {} asks",
2131    ///          order_book.instrument_name,
2132    ///          order_book.bids.len(),
2133    ///          order_book.asks.len());
2134    /// # Ok(())
2135    /// # }
2136    /// ```
2137    pub async fn get_order_book_by_instrument_id(
2138        &self,
2139        instrument_id: u32,
2140        depth: Option<u32>,
2141    ) -> Result<OrderBook, HttpError> {
2142        let mut url = format!(
2143            "{}{}?instrument_id={}",
2144            self.base_url(),
2145            GET_ORDER_BOOK_BY_INSTRUMENT_ID,
2146            instrument_id
2147        );
2148
2149        if let Some(depth) = depth {
2150            url.push_str(&format!("&depth={}", depth));
2151        }
2152
2153        let response = self
2154            .http_client()
2155            .get(&url)
2156            .send()
2157            .await
2158            .map_err(|e| HttpError::NetworkError(e.to_string()))?;
2159
2160        if !response.status().is_success() {
2161            let error_text = response
2162                .text()
2163                .await
2164                .unwrap_or_else(|_| "Unknown error".to_string());
2165            return Err(HttpError::RequestFailed(format!(
2166                "Get order book by instrument ID failed: {}",
2167                error_text
2168            )));
2169        }
2170
2171        let api_response: ApiResponse<OrderBook> = response
2172            .json()
2173            .await
2174            .map_err(|e| HttpError::InvalidResponse(e.to_string()))?;
2175
2176        if let Some(error) = api_response.error {
2177            return Err(HttpError::RequestFailed(format!(
2178                "API error: {} - {}",
2179                error.code, error.message
2180            )));
2181        }
2182
2183        api_response
2184            .result
2185            .ok_or_else(|| HttpError::InvalidResponse("No order book data in response".to_string()))
2186    }
2187}