Skip to main content

binance_sdk/alpha/rest_api/apis/
market_data_api.rs

1/*
2 * Alpha Trading REST API
3 *
4 * APIs for Binance Alpha Trading.
5 *
6 * The version of the OpenAPI document: 1.0.0
7 *
8 *
9 * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
10 * https://openapi-generator.tech
11 * Do not edit the class manually.
12 */
13
14#![allow(unused_imports)]
15use async_trait::async_trait;
16use derive_builder::Builder;
17use reqwest;
18use rust_decimal::prelude::*;
19use serde::{Deserialize, Serialize};
20use serde_json::{Value, json};
21use std::collections::BTreeMap;
22
23use crate::alpha::rest_api::models;
24use crate::common::{
25    config::ConfigurationRestApi,
26    models::{ParamBuildError, RestApiResponse},
27    utils::send_request,
28};
29
30const HAS_TIME_UNIT: bool = false;
31
32#[async_trait]
33pub trait MarketDataApi: Send + Sync {
34    async fn aggregated_trades(
35        &self,
36        params: AggregatedTradesParams,
37    ) -> anyhow::Result<RestApiResponse<models::AggregatedTradesResponse>>;
38    async fn full_depth(
39        &self,
40        params: FullDepthParams,
41    ) -> anyhow::Result<RestApiResponse<models::FullDepthResponse>>;
42    async fn get_exchange_info(
43        &self,
44    ) -> anyhow::Result<RestApiResponse<models::GetExchangeInfoResponse>>;
45    async fn klines(
46        &self,
47        params: KlinesParams,
48    ) -> anyhow::Result<RestApiResponse<models::KlinesResponse>>;
49    async fn ticker(
50        &self,
51        params: TickerParams,
52    ) -> anyhow::Result<RestApiResponse<models::TickerResponse>>;
53    async fn token_list(&self) -> anyhow::Result<RestApiResponse<models::TokenListResponse>>;
54}
55
56#[derive(Debug, Clone)]
57pub struct MarketDataApiClient {
58    configuration: ConfigurationRestApi,
59}
60
61impl MarketDataApiClient {
62    pub fn new(configuration: ConfigurationRestApi) -> Self {
63        Self { configuration }
64    }
65}
66
67#[allow(non_camel_case_types)]
68#[derive(Debug, Clone, Serialize, Deserialize)]
69pub enum FullDepthLimitEnum {
70    #[serde(rename = "5")]
71    Limit5,
72    #[serde(rename = "10")]
73    Limit10,
74    #[serde(rename = "20")]
75    Limit20,
76    #[serde(rename = "50")]
77    Limit50,
78    #[serde(rename = "100")]
79    Limit100,
80    #[serde(rename = "500")]
81    Limit500,
82    #[serde(rename = "1000")]
83    Limit1000,
84}
85
86impl FullDepthLimitEnum {
87    #[must_use]
88    pub fn as_str(&self) -> &'static str {
89        match self {
90            Self::Limit5 => "5",
91            Self::Limit10 => "10",
92            Self::Limit20 => "20",
93            Self::Limit50 => "50",
94            Self::Limit100 => "100",
95            Self::Limit500 => "500",
96            Self::Limit1000 => "1000",
97        }
98    }
99}
100
101impl std::str::FromStr for FullDepthLimitEnum {
102    type Err = Box<dyn std::error::Error + Send + Sync>;
103
104    fn from_str(s: &str) -> Result<Self, Self::Err> {
105        match s {
106            "5" => Ok(Self::Limit5),
107            "10" => Ok(Self::Limit10),
108            "20" => Ok(Self::Limit20),
109            "50" => Ok(Self::Limit50),
110            "100" => Ok(Self::Limit100),
111            "500" => Ok(Self::Limit500),
112            "1000" => Ok(Self::Limit1000),
113            other => Err(format!("invalid FullDepthLimitEnum: {}", other).into()),
114        }
115    }
116}
117
118#[allow(non_camel_case_types)]
119#[derive(Debug, Clone, Serialize, Deserialize)]
120pub enum KlinesIntervalEnum {
121    #[serde(rename = "1s")]
122    Interval1s,
123    #[serde(rename = "15s")]
124    Interval15s,
125    #[serde(rename = "1m")]
126    Interval1m,
127    #[serde(rename = "3m")]
128    Interval3m,
129    #[serde(rename = "5m")]
130    Interval5m,
131    #[serde(rename = "15m")]
132    Interval15m,
133    #[serde(rename = "30m")]
134    Interval30m,
135    #[serde(rename = "1h")]
136    Interval1h,
137    #[serde(rename = "2h")]
138    Interval2h,
139    #[serde(rename = "4h")]
140    Interval4h,
141    #[serde(rename = "6h")]
142    Interval6h,
143    #[serde(rename = "8h")]
144    Interval8h,
145    #[serde(rename = "12h")]
146    Interval12h,
147    #[serde(rename = "1d")]
148    Interval1d,
149    #[serde(rename = "3d")]
150    Interval3d,
151    #[serde(rename = "1w")]
152    Interval1w,
153    #[serde(rename = "1M")]
154    Interval1M,
155}
156
157impl KlinesIntervalEnum {
158    #[must_use]
159    pub fn as_str(&self) -> &'static str {
160        match self {
161            Self::Interval1s => "1s",
162            Self::Interval15s => "15s",
163            Self::Interval1m => "1m",
164            Self::Interval3m => "3m",
165            Self::Interval5m => "5m",
166            Self::Interval15m => "15m",
167            Self::Interval30m => "30m",
168            Self::Interval1h => "1h",
169            Self::Interval2h => "2h",
170            Self::Interval4h => "4h",
171            Self::Interval6h => "6h",
172            Self::Interval8h => "8h",
173            Self::Interval12h => "12h",
174            Self::Interval1d => "1d",
175            Self::Interval3d => "3d",
176            Self::Interval1w => "1w",
177            Self::Interval1M => "1M",
178        }
179    }
180}
181
182impl std::str::FromStr for KlinesIntervalEnum {
183    type Err = Box<dyn std::error::Error + Send + Sync>;
184
185    fn from_str(s: &str) -> Result<Self, Self::Err> {
186        match s {
187            "1s" => Ok(Self::Interval1s),
188            "15s" => Ok(Self::Interval15s),
189            "1m" => Ok(Self::Interval1m),
190            "3m" => Ok(Self::Interval3m),
191            "5m" => Ok(Self::Interval5m),
192            "15m" => Ok(Self::Interval15m),
193            "30m" => Ok(Self::Interval30m),
194            "1h" => Ok(Self::Interval1h),
195            "2h" => Ok(Self::Interval2h),
196            "4h" => Ok(Self::Interval4h),
197            "6h" => Ok(Self::Interval6h),
198            "8h" => Ok(Self::Interval8h),
199            "12h" => Ok(Self::Interval12h),
200            "1d" => Ok(Self::Interval1d),
201            "3d" => Ok(Self::Interval3d),
202            "1w" => Ok(Self::Interval1w),
203            "1M" => Ok(Self::Interval1M),
204            other => Err(format!("invalid KlinesIntervalEnum: {}", other).into()),
205        }
206    }
207}
208
209/// Request parameters for the [`aggregated_trades`] operation.
210///
211/// This struct holds all of the inputs you can pass when calling
212/// [`aggregated_trades`](#method.aggregated_trades).
213#[derive(Clone, Debug, Builder, Deserialize)]
214#[builder(pattern = "owned", build_fn(error = "ParamBuildError"))]
215pub struct AggregatedTradesParams {
216    /// Trading pair symbol, e.g. `ALPHA_118USDC` (use token ID from Token List).
217    ///
218    /// This field is **required.
219    #[builder(setter(into))]
220    #[serde(rename = "symbol")]
221    pub symbol: String,
222    /// Starting aggregate trade ID to fetch from.
223    ///
224    /// This field is **optional.
225    #[builder(setter(into), default)]
226    #[serde(rename = "fromId", default)]
227    pub from_id: Option<i64>,
228    /// Start timestamp in milliseconds.
229    ///
230    /// This field is **optional.
231    #[builder(setter(into), default)]
232    #[serde(rename = "startTime", default)]
233    pub start_time: Option<i64>,
234    /// End timestamp in milliseconds.
235    ///
236    /// This field is **optional.
237    #[builder(setter(into), default)]
238    #[serde(rename = "endTime", default)]
239    pub end_time: Option<i64>,
240    /// Number of results to return.
241    ///
242    /// This field is **optional.
243    #[builder(setter(into), default)]
244    #[serde(rename = "limit", default)]
245    pub limit: Option<i64>,
246}
247
248impl AggregatedTradesParams {
249    /// Create a builder for [`aggregated_trades`].
250    ///
251    /// Required parameters:
252    ///
253    /// * `symbol` — Trading pair symbol, e.g. `ALPHA_118USDC` (use token ID from Token List).
254    ///
255    #[must_use]
256    pub fn builder(symbol: String) -> AggregatedTradesParamsBuilder {
257        AggregatedTradesParamsBuilder::default().symbol(symbol)
258    }
259}
260/// Request parameters for the [`full_depth`] operation.
261///
262/// This struct holds all of the inputs you can pass when calling
263/// [`full_depth`](#method.full_depth).
264#[derive(Clone, Debug, Builder, Deserialize)]
265#[builder(pattern = "owned", build_fn(error = "ParamBuildError"))]
266pub struct FullDepthParams {
267    /// Trading pair symbol, e.g. `ALPHA_175USDT` (use token ID from Token List).
268    ///
269    /// This field is **required.
270    #[builder(setter(into))]
271    #[serde(rename = "symbol")]
272    pub symbol: String,
273    /// Number of price levels to return. Valid values: 5, 10, 20, 50, 100, 500, 1000.
274    ///
275    /// This field is **optional.
276    #[builder(setter(into), default)]
277    #[serde(rename = "limit", default)]
278    pub limit: Option<FullDepthLimitEnum>,
279}
280
281impl FullDepthParams {
282    /// Create a builder for [`full_depth`].
283    ///
284    /// Required parameters:
285    ///
286    /// * `symbol` — Trading pair symbol, e.g. `ALPHA_175USDT` (use token ID from Token List).
287    ///
288    #[must_use]
289    pub fn builder(symbol: String) -> FullDepthParamsBuilder {
290        FullDepthParamsBuilder::default().symbol(symbol)
291    }
292}
293/// Request parameters for the [`klines`] operation.
294///
295/// This struct holds all of the inputs you can pass when calling
296/// [`klines`](#method.klines).
297#[derive(Clone, Debug, Builder, Deserialize)]
298#[builder(pattern = "owned", build_fn(error = "ParamBuildError"))]
299pub struct KlinesParams {
300    /// Trading pair symbol, e.g. `ALPHA_175USDT` (use token ID from Token List).
301    ///
302    /// This field is **required.
303    #[builder(setter(into))]
304    #[serde(rename = "symbol")]
305    pub symbol: String,
306    /// Kline interval.
307    ///
308    /// This field is **required.
309    #[builder(setter(into))]
310    #[serde(rename = "interval")]
311    pub interval: KlinesIntervalEnum,
312    /// Number of klines to return.
313    ///
314    /// This field is **optional.
315    #[builder(setter(into), default)]
316    #[serde(rename = "limit", default)]
317    pub limit: Option<i64>,
318    /// Start timestamp in milliseconds.
319    ///
320    /// This field is **optional.
321    #[builder(setter(into), default)]
322    #[serde(rename = "startTime", default)]
323    pub start_time: Option<i64>,
324    /// End timestamp in milliseconds.
325    ///
326    /// This field is **optional.
327    #[builder(setter(into), default)]
328    #[serde(rename = "endTime", default)]
329    pub end_time: Option<i64>,
330}
331
332impl KlinesParams {
333    /// Create a builder for [`klines`].
334    ///
335    /// Required parameters:
336    ///
337    /// * `symbol` — Trading pair symbol, e.g. `ALPHA_175USDT` (use token ID from Token List).
338    /// * `interval` — Kline interval.
339    ///
340    #[must_use]
341    pub fn builder(symbol: String, interval: KlinesIntervalEnum) -> KlinesParamsBuilder {
342        KlinesParamsBuilder::default()
343            .symbol(symbol)
344            .interval(interval)
345    }
346}
347/// Request parameters for the [`ticker`] operation.
348///
349/// This struct holds all of the inputs you can pass when calling
350/// [`ticker`](#method.ticker).
351#[derive(Clone, Debug, Builder, Deserialize)]
352#[builder(pattern = "owned", build_fn(error = "ParamBuildError"))]
353pub struct TickerParams {
354    /// Trading pair symbol, e.g. `ALPHA_175USDT` (use token ID from Token List).
355    ///
356    /// This field is **required.
357    #[builder(setter(into))]
358    #[serde(rename = "symbol")]
359    pub symbol: String,
360}
361
362impl TickerParams {
363    /// Create a builder for [`ticker`].
364    ///
365    /// Required parameters:
366    ///
367    /// * `symbol` — Trading pair symbol, e.g. `ALPHA_175USDT` (use token ID from Token List).
368    ///
369    #[must_use]
370    pub fn builder(symbol: String) -> TickerParamsBuilder {
371        TickerParamsBuilder::default().symbol(symbol)
372    }
373}
374
375#[async_trait]
376impl MarketDataApi for MarketDataApiClient {
377    async fn aggregated_trades(
378        &self,
379        params: AggregatedTradesParams,
380    ) -> anyhow::Result<RestApiResponse<models::AggregatedTradesResponse>> {
381        let AggregatedTradesParams {
382            symbol,
383            from_id,
384            start_time,
385            end_time,
386            limit,
387        } = params;
388
389        let mut query_params = BTreeMap::new();
390        let body_params = BTreeMap::new();
391
392        query_params.insert("symbol".to_string(), json!(symbol));
393
394        if let Some(rw) = from_id {
395            query_params.insert("fromId".to_string(), json!(rw));
396        }
397
398        if let Some(rw) = start_time {
399            query_params.insert("startTime".to_string(), json!(rw));
400        }
401
402        if let Some(rw) = end_time {
403            query_params.insert("endTime".to_string(), json!(rw));
404        }
405
406        if let Some(rw) = limit {
407            query_params.insert("limit".to_string(), json!(rw));
408        }
409
410        send_request::<models::AggregatedTradesResponse>(
411            &self.configuration,
412            "/bapi/defi/v1/public/alpha-trade/agg-trades",
413            reqwest::Method::GET,
414            query_params,
415            body_params,
416            if HAS_TIME_UNIT {
417                self.configuration.time_unit
418            } else {
419                None
420            },
421            false,
422        )
423        .await
424    }
425
426    async fn full_depth(
427        &self,
428        params: FullDepthParams,
429    ) -> anyhow::Result<RestApiResponse<models::FullDepthResponse>> {
430        let FullDepthParams { symbol, limit } = params;
431
432        let mut query_params = BTreeMap::new();
433        let body_params = BTreeMap::new();
434
435        query_params.insert("symbol".to_string(), json!(symbol));
436
437        if let Some(rw) = limit {
438            query_params.insert("limit".to_string(), json!(rw));
439        }
440
441        send_request::<models::FullDepthResponse>(
442            &self.configuration,
443            "/bapi/defi/v1/public/alpha-trade/fullDepth",
444            reqwest::Method::GET,
445            query_params,
446            body_params,
447            if HAS_TIME_UNIT {
448                self.configuration.time_unit
449            } else {
450                None
451            },
452            false,
453        )
454        .await
455    }
456
457    async fn get_exchange_info(
458        &self,
459    ) -> anyhow::Result<RestApiResponse<models::GetExchangeInfoResponse>> {
460        let query_params = BTreeMap::new();
461        let body_params = BTreeMap::new();
462
463        send_request::<models::GetExchangeInfoResponse>(
464            &self.configuration,
465            "/bapi/defi/v1/public/alpha-trade/get-exchange-info",
466            reqwest::Method::GET,
467            query_params,
468            body_params,
469            if HAS_TIME_UNIT {
470                self.configuration.time_unit
471            } else {
472                None
473            },
474            false,
475        )
476        .await
477    }
478
479    async fn klines(
480        &self,
481        params: KlinesParams,
482    ) -> anyhow::Result<RestApiResponse<models::KlinesResponse>> {
483        let KlinesParams {
484            symbol,
485            interval,
486            limit,
487            start_time,
488            end_time,
489        } = params;
490
491        let mut query_params = BTreeMap::new();
492        let body_params = BTreeMap::new();
493
494        query_params.insert("symbol".to_string(), json!(symbol));
495
496        query_params.insert("interval".to_string(), json!(interval));
497
498        if let Some(rw) = limit {
499            query_params.insert("limit".to_string(), json!(rw));
500        }
501
502        if let Some(rw) = start_time {
503            query_params.insert("startTime".to_string(), json!(rw));
504        }
505
506        if let Some(rw) = end_time {
507            query_params.insert("endTime".to_string(), json!(rw));
508        }
509
510        send_request::<models::KlinesResponse>(
511            &self.configuration,
512            "/bapi/defi/v1/public/alpha-trade/klines",
513            reqwest::Method::GET,
514            query_params,
515            body_params,
516            if HAS_TIME_UNIT {
517                self.configuration.time_unit
518            } else {
519                None
520            },
521            false,
522        )
523        .await
524    }
525
526    async fn ticker(
527        &self,
528        params: TickerParams,
529    ) -> anyhow::Result<RestApiResponse<models::TickerResponse>> {
530        let TickerParams { symbol } = params;
531
532        let mut query_params = BTreeMap::new();
533        let body_params = BTreeMap::new();
534
535        query_params.insert("symbol".to_string(), json!(symbol));
536
537        send_request::<models::TickerResponse>(
538            &self.configuration,
539            "/bapi/defi/v1/public/alpha-trade/ticker",
540            reqwest::Method::GET,
541            query_params,
542            body_params,
543            if HAS_TIME_UNIT {
544                self.configuration.time_unit
545            } else {
546                None
547            },
548            false,
549        )
550        .await
551    }
552
553    async fn token_list(&self) -> anyhow::Result<RestApiResponse<models::TokenListResponse>> {
554        let query_params = BTreeMap::new();
555        let body_params = BTreeMap::new();
556
557        send_request::<models::TokenListResponse>(
558            &self.configuration,
559            "/bapi/defi/v1/public/wallet-direct/buw/wallet/cex/alpha/all/token/list",
560            reqwest::Method::GET,
561            query_params,
562            body_params,
563            if HAS_TIME_UNIT {
564                self.configuration.time_unit
565            } else {
566                None
567            },
568            false,
569        )
570        .await
571    }
572}
573
574#[cfg(all(test, feature = "alpha"))]
575mod tests {
576    use super::*;
577    use crate::TOKIO_SHARED_RT;
578    use crate::{errors::ConnectorError, models::DataFuture, models::RestApiRateLimit};
579    use async_trait::async_trait;
580    use std::collections::HashMap;
581
582    struct DummyRestApiResponse<T> {
583        inner: Box<dyn FnOnce() -> DataFuture<Result<T, ConnectorError>> + Send + Sync>,
584        status: u16,
585        headers: HashMap<String, String>,
586        rate_limits: Option<Vec<RestApiRateLimit>>,
587    }
588
589    impl<T> From<DummyRestApiResponse<T>> for RestApiResponse<T> {
590        fn from(dummy: DummyRestApiResponse<T>) -> Self {
591            Self {
592                data_fn: dummy.inner,
593                status: dummy.status,
594                headers: dummy.headers,
595                rate_limits: dummy.rate_limits,
596            }
597        }
598    }
599
600    struct MockMarketDataApiClient {
601        force_error: bool,
602    }
603
604    #[async_trait]
605    impl MarketDataApi for MockMarketDataApiClient {
606        async fn aggregated_trades(
607            &self,
608            _params: AggregatedTradesParams,
609        ) -> anyhow::Result<RestApiResponse<models::AggregatedTradesResponse>> {
610            if self.force_error {
611                return Err(ConnectorError::ConnectorClientError {
612                    msg: "ResponseError".to_string(),
613                    code: None,
614                }
615                .into());
616            }
617
618            let resp_json: Value = serde_json::from_str(r#"{"code":"000000","message":"","messageDetail":"","data":[{"a":58470,"p":"1.00","q":"1.00","f":58470,"l":58665,"T":1752568680000,"m":true}]}"#).unwrap_or_else(|_| serde_json::json!({}));
619            let dummy_response: models::AggregatedTradesResponse =
620                serde_json::from_value(resp_json.clone())
621                    .expect("should parse into models::AggregatedTradesResponse");
622
623            let dummy = DummyRestApiResponse {
624                inner: Box::new(move || Box::pin(async move { Ok(dummy_response) })),
625                status: 200,
626                headers: HashMap::new(),
627                rate_limits: None,
628            };
629
630            Ok(dummy.into())
631        }
632
633        async fn full_depth(
634            &self,
635            _params: FullDepthParams,
636        ) -> anyhow::Result<RestApiResponse<models::FullDepthResponse>> {
637            if self.force_error {
638                return Err(ConnectorError::ConnectorClientError {
639                    msg: "ResponseError".to_string(),
640                    code: None,
641                }
642                .into());
643            }
644
645            let resp_json: Value = serde_json::from_str(r#"{"code":"000000","message":"message","messageDetail":"messageDetail","success":true,"data":{"lastUpdateId":47534656223,"symbol":"ALPHA_175USDT","bids":[["0.00040161","365980.85000000"]],"asks":[["0.00046996","61994.70000000"]],"E":1775027836086,"T":1775027836072}}"#).unwrap_or_else(|_| serde_json::json!({}));
646            let dummy_response: models::FullDepthResponse =
647                serde_json::from_value(resp_json.clone())
648                    .expect("should parse into models::FullDepthResponse");
649
650            let dummy = DummyRestApiResponse {
651                inner: Box::new(move || Box::pin(async move { Ok(dummy_response) })),
652                status: 200,
653                headers: HashMap::new(),
654                rate_limits: None,
655            };
656
657            Ok(dummy.into())
658        }
659
660        async fn get_exchange_info(
661            &self,
662        ) -> anyhow::Result<RestApiResponse<models::GetExchangeInfoResponse>> {
663            if self.force_error {
664                return Err(ConnectorError::ConnectorClientError {
665                    msg: "ResponseError".to_string(),
666                    code: None,
667                }
668                .into());
669            }
670
671            let resp_json: Value = serde_json::from_str(r#"{"code":"000000","message":"","messageDetail":"","success":true,"data":{"timezone":"UTC","assets":[{"asset":"USDT"}],"symbols":[{"symbol":"ALPHA_105USDT","status":"TRADING","baseAsset":"ALPHA_105","quoteAsset":"USDT","pricePrecision":8,"quantityPrecision":8,"baseAssetPrecision":8,"quotePrecision":8,"filters":[{"filterType":"PRICE_FILTER","minPrice":"0.00000001","maxPrice":"1000","tickSize":"0.00000001","stepSize":"0.01000000","maxQty":"277778","minQty":"0.01000000","limit":200,"minNotional":"0.1","maxNotional":"1000000","multiplierDown":"0.20000","multiplierUp":"5","bidMultiplierUp":"5","askMultiplierUp":"5","bidMultiplierDown":"0.20000","askMultiplierDown":"0.20000"}],"orderTypes":["LIMIT"]}],"orderTypes":""}}"#).unwrap_or_else(|_| serde_json::json!({}));
672            let dummy_response: models::GetExchangeInfoResponse =
673                serde_json::from_value(resp_json.clone())
674                    .expect("should parse into models::GetExchangeInfoResponse");
675
676            let dummy = DummyRestApiResponse {
677                inner: Box::new(move || Box::pin(async move { Ok(dummy_response) })),
678                status: 200,
679                headers: HashMap::new(),
680                rate_limits: None,
681            };
682
683            Ok(dummy.into())
684        }
685
686        async fn klines(
687            &self,
688            _params: KlinesParams,
689        ) -> anyhow::Result<RestApiResponse<models::KlinesResponse>> {
690            if self.force_error {
691                return Err(ConnectorError::ConnectorClientError {
692                    msg: "ResponseError".to_string(),
693                    code: None,
694                }
695                .into());
696            }
697
698            let resp_json: Value = serde_json::from_str(r#"{"code":"000000","message":"","messageDetail":"","success":true,"data":[["1752642000000"]]}"#).unwrap_or_else(|_| serde_json::json!({}));
699            let dummy_response: models::KlinesResponse = serde_json::from_value(resp_json.clone())
700                .expect("should parse into models::KlinesResponse");
701
702            let dummy = DummyRestApiResponse {
703                inner: Box::new(move || Box::pin(async move { Ok(dummy_response) })),
704                status: 200,
705                headers: HashMap::new(),
706                rate_limits: None,
707            };
708
709            Ok(dummy.into())
710        }
711
712        async fn ticker(
713            &self,
714            _params: TickerParams,
715        ) -> anyhow::Result<RestApiResponse<models::TickerResponse>> {
716            if self.force_error {
717                return Err(ConnectorError::ConnectorClientError {
718                    msg: "ResponseError".to_string(),
719                    code: None,
720                }
721                .into());
722            }
723
724            let resp_json: Value = serde_json::from_str(r#"{"code":"000000","message":"","messageDetail":"","data":{"symbol":"ALPHA_175USDT","priceChange":"-0.00007172","priceChangePercent":"-8.841","weightedAvgPrice":"0.00079608","lastPrice":"0.00073954","lastQty":"12600.43000000","openPrice":"0.00081126","highPrice":"0.00081126","lowPrice":"0.00073954","volume":"1204754.30000000","quoteVolume":"959.07729927","openTime":1768808100000,"closeTime":1768893244772,"firstId":93742,"lastId":93768,"count":38},"success":true}"#).unwrap_or_else(|_| serde_json::json!({}));
725            let dummy_response: models::TickerResponse = serde_json::from_value(resp_json.clone())
726                .expect("should parse into models::TickerResponse");
727
728            let dummy = DummyRestApiResponse {
729                inner: Box::new(move || Box::pin(async move { Ok(dummy_response) })),
730                status: 200,
731                headers: HashMap::new(),
732                rate_limits: None,
733            };
734
735            Ok(dummy.into())
736        }
737
738        async fn token_list(&self) -> anyhow::Result<RestApiResponse<models::TokenListResponse>> {
739            if self.force_error {
740                return Err(ConnectorError::ConnectorClientError {
741                    msg: "ResponseError".to_string(),
742                    code: None,
743                }
744                .into());
745            }
746
747            let resp_json: Value = serde_json::from_str(r#"{"code":"000000","message":"","messageDetail":"","success":true,"data":[{"tokenId":"3F350C8B3621770A673159B7A19BC034","chainId":"56","chainIconUrl":"https://bin.bnbstatic.com/image/admin_mgs_image_upload/20250228/d0216ce4-a3e9-4bda-8937-4a6aa943ccf2.png","chainName":"BSC","contractAddress":"0xcf640fdf9b3d9e45cbd69fda91d7e22579c14444","name":"gorilla","symbol":"gorilla","iconUrl":"https://bin.bnbstatic.com/images/web3-data/public/token/logos/248b5406f88a4ee28913a29107875339.png","price":"0.00080595003978242023","percentChange24h":"-7.42","volume24h":"14847.711222633409558029675","marketCap":"805950.03978242","fdv":"805950.03978242","liquidity":"263192.72813121626034","totalSupply":"1000000000","circulatingSupply":"1000000000","holders":"7120","decimals":18,"listingCex":false,"hotTag":false,"cexCoinName":"","canTransfer":false,"denomination":1,"offline":false,"tradeDecimal":8,"alphaId":"ALPHA_175","offsell":false,"priceHigh24h":"0.00088873864475645879","priceLow24h":"0.0008020805165487083","count24h":"166","onlineTge":false,"onlineAirdrop":false,"score":1,"cexOffDisplay":false,"stockState":false,"listingTime":1746686700000,"mulPoint":1,"bnExclusiveState":false}]}"#).unwrap_or_else(|_| serde_json::json!({}));
748            let dummy_response: models::TokenListResponse =
749                serde_json::from_value(resp_json.clone())
750                    .expect("should parse into models::TokenListResponse");
751
752            let dummy = DummyRestApiResponse {
753                inner: Box::new(move || Box::pin(async move { Ok(dummy_response) })),
754                status: 200,
755                headers: HashMap::new(),
756                rate_limits: None,
757            };
758
759            Ok(dummy.into())
760        }
761    }
762
763    #[test]
764    fn aggregated_trades_required_params_success() {
765        TOKIO_SHARED_RT.block_on(async {
766            let client = MockMarketDataApiClient { force_error: false };
767
768            let params = AggregatedTradesParams::builder("ALPHA_118USDC".to_string(),).build().unwrap();
769
770            let resp_json: Value = serde_json::from_str(r#"{"code":"000000","message":"","messageDetail":"","data":[{"a":58470,"p":"1.00","q":"1.00","f":58470,"l":58665,"T":1752568680000,"m":true}]}"#).unwrap_or_else(|_| serde_json::json!({}));
771            let expected_response : models::AggregatedTradesResponse = serde_json::from_value(resp_json.clone()).expect("should parse into models::AggregatedTradesResponse");
772
773            let resp = client.aggregated_trades(params).await.expect("Expected a response");
774            let data_future = resp.data();
775            let actual_response = data_future.await.unwrap();
776            assert_eq!(actual_response, expected_response);
777        });
778    }
779
780    #[test]
781    fn aggregated_trades_optional_params_success() {
782        TOKIO_SHARED_RT.block_on(async {
783            let client = MockMarketDataApiClient { force_error: false };
784
785            let params = AggregatedTradesParams::builder("ALPHA_118USDC".to_string(),).from_id(58470).start_time(1752568680000).end_time(1752572280000).limit(500).build().unwrap();
786
787            let resp_json: Value = serde_json::from_str(r#"{"code":"000000","message":"","messageDetail":"","data":[{"a":58470,"p":"1.00","q":"1.00","f":58470,"l":58665,"T":1752568680000,"m":true}]}"#).unwrap_or_else(|_| serde_json::json!({}));
788            let expected_response : models::AggregatedTradesResponse = serde_json::from_value(resp_json.clone()).expect("should parse into models::AggregatedTradesResponse");
789
790            let resp = client.aggregated_trades(params).await.expect("Expected a response");
791            let data_future = resp.data();
792            let actual_response = data_future.await.unwrap();
793            assert_eq!(actual_response, expected_response);
794        });
795    }
796
797    #[test]
798    fn aggregated_trades_response_error() {
799        TOKIO_SHARED_RT.block_on(async {
800            let client = MockMarketDataApiClient { force_error: true };
801
802            let params = AggregatedTradesParams::builder("ALPHA_118USDC".to_string())
803                .build()
804                .unwrap();
805
806            match client.aggregated_trades(params).await {
807                Ok(_) => panic!("Expected an error"),
808                Err(err) => {
809                    assert_eq!(err.to_string(), "Connector client error: ResponseError");
810                }
811            }
812        });
813    }
814
815    #[test]
816    fn full_depth_required_params_success() {
817        TOKIO_SHARED_RT.block_on(async {
818            let client = MockMarketDataApiClient { force_error: false };
819
820            let params = FullDepthParams::builder("ALPHA_175USDT".to_string(),).build().unwrap();
821
822            let resp_json: Value = serde_json::from_str(r#"{"code":"000000","message":"message","messageDetail":"messageDetail","success":true,"data":{"lastUpdateId":47534656223,"symbol":"ALPHA_175USDT","bids":[["0.00040161","365980.85000000"]],"asks":[["0.00046996","61994.70000000"]],"E":1775027836086,"T":1775027836072}}"#).unwrap_or_else(|_| serde_json::json!({}));
823            let expected_response : models::FullDepthResponse = serde_json::from_value(resp_json.clone()).expect("should parse into models::FullDepthResponse");
824
825            let resp = client.full_depth(params).await.expect("Expected a response");
826            let data_future = resp.data();
827            let actual_response = data_future.await.unwrap();
828            assert_eq!(actual_response, expected_response);
829        });
830    }
831
832    #[test]
833    fn full_depth_optional_params_success() {
834        TOKIO_SHARED_RT.block_on(async {
835            let client = MockMarketDataApiClient { force_error: false };
836
837            let params = FullDepthParams::builder("ALPHA_175USDT".to_string(),).limit(FullDepthLimitEnum::Limit5).build().unwrap();
838
839            let resp_json: Value = serde_json::from_str(r#"{"code":"000000","message":"message","messageDetail":"messageDetail","success":true,"data":{"lastUpdateId":47534656223,"symbol":"ALPHA_175USDT","bids":[["0.00040161","365980.85000000"]],"asks":[["0.00046996","61994.70000000"]],"E":1775027836086,"T":1775027836072}}"#).unwrap_or_else(|_| serde_json::json!({}));
840            let expected_response : models::FullDepthResponse = serde_json::from_value(resp_json.clone()).expect("should parse into models::FullDepthResponse");
841
842            let resp = client.full_depth(params).await.expect("Expected a response");
843            let data_future = resp.data();
844            let actual_response = data_future.await.unwrap();
845            assert_eq!(actual_response, expected_response);
846        });
847    }
848
849    #[test]
850    fn full_depth_response_error() {
851        TOKIO_SHARED_RT.block_on(async {
852            let client = MockMarketDataApiClient { force_error: true };
853
854            let params = FullDepthParams::builder("ALPHA_175USDT".to_string())
855                .build()
856                .unwrap();
857
858            match client.full_depth(params).await {
859                Ok(_) => panic!("Expected an error"),
860                Err(err) => {
861                    assert_eq!(err.to_string(), "Connector client error: ResponseError");
862                }
863            }
864        });
865    }
866
867    #[test]
868    fn get_exchange_info_required_params_success() {
869        TOKIO_SHARED_RT.block_on(async {
870            let client = MockMarketDataApiClient { force_error: false };
871
872
873            let resp_json: Value = serde_json::from_str(r#"{"code":"000000","message":"","messageDetail":"","success":true,"data":{"timezone":"UTC","assets":[{"asset":"USDT"}],"symbols":[{"symbol":"ALPHA_105USDT","status":"TRADING","baseAsset":"ALPHA_105","quoteAsset":"USDT","pricePrecision":8,"quantityPrecision":8,"baseAssetPrecision":8,"quotePrecision":8,"filters":[{"filterType":"PRICE_FILTER","minPrice":"0.00000001","maxPrice":"1000","tickSize":"0.00000001","stepSize":"0.01000000","maxQty":"277778","minQty":"0.01000000","limit":200,"minNotional":"0.1","maxNotional":"1000000","multiplierDown":"0.20000","multiplierUp":"5","bidMultiplierUp":"5","askMultiplierUp":"5","bidMultiplierDown":"0.20000","askMultiplierDown":"0.20000"}],"orderTypes":["LIMIT"]}],"orderTypes":""}}"#).unwrap_or_else(|_| serde_json::json!({}));
874            let expected_response : models::GetExchangeInfoResponse = serde_json::from_value(resp_json.clone()).expect("should parse into models::GetExchangeInfoResponse");
875
876            let resp = client.get_exchange_info().await.expect("Expected a response");
877            let data_future = resp.data();
878            let actual_response = data_future.await.unwrap();
879            assert_eq!(actual_response, expected_response);
880        });
881    }
882
883    #[test]
884    fn get_exchange_info_optional_params_success() {
885        TOKIO_SHARED_RT.block_on(async {
886            let client = MockMarketDataApiClient { force_error: false };
887
888
889            let resp_json: Value = serde_json::from_str(r#"{"code":"000000","message":"","messageDetail":"","success":true,"data":{"timezone":"UTC","assets":[{"asset":"USDT"}],"symbols":[{"symbol":"ALPHA_105USDT","status":"TRADING","baseAsset":"ALPHA_105","quoteAsset":"USDT","pricePrecision":8,"quantityPrecision":8,"baseAssetPrecision":8,"quotePrecision":8,"filters":[{"filterType":"PRICE_FILTER","minPrice":"0.00000001","maxPrice":"1000","tickSize":"0.00000001","stepSize":"0.01000000","maxQty":"277778","minQty":"0.01000000","limit":200,"minNotional":"0.1","maxNotional":"1000000","multiplierDown":"0.20000","multiplierUp":"5","bidMultiplierUp":"5","askMultiplierUp":"5","bidMultiplierDown":"0.20000","askMultiplierDown":"0.20000"}],"orderTypes":["LIMIT"]}],"orderTypes":""}}"#).unwrap_or_else(|_| serde_json::json!({}));
890            let expected_response : models::GetExchangeInfoResponse = serde_json::from_value(resp_json.clone()).expect("should parse into models::GetExchangeInfoResponse");
891
892            let resp = client.get_exchange_info().await.expect("Expected a response");
893            let data_future = resp.data();
894            let actual_response = data_future.await.unwrap();
895            assert_eq!(actual_response, expected_response);
896        });
897    }
898
899    #[test]
900    fn get_exchange_info_response_error() {
901        TOKIO_SHARED_RT.block_on(async {
902            let client = MockMarketDataApiClient { force_error: true };
903
904            match client.get_exchange_info().await {
905                Ok(_) => panic!("Expected an error"),
906                Err(err) => {
907                    assert_eq!(err.to_string(), "Connector client error: ResponseError");
908                }
909            }
910        });
911    }
912
913    #[test]
914    fn klines_required_params_success() {
915        TOKIO_SHARED_RT.block_on(async {
916            let client = MockMarketDataApiClient { force_error: false };
917
918            let params = KlinesParams::builder("ALPHA_175USDT".to_string(),KlinesIntervalEnum::Interval1s,).build().unwrap();
919
920            let resp_json: Value = serde_json::from_str(r#"{"code":"000000","message":"","messageDetail":"","success":true,"data":[["1752642000000"]]}"#).unwrap_or_else(|_| serde_json::json!({}));
921            let expected_response : models::KlinesResponse = serde_json::from_value(resp_json.clone()).expect("should parse into models::KlinesResponse");
922
923            let resp = client.klines(params).await.expect("Expected a response");
924            let data_future = resp.data();
925            let actual_response = data_future.await.unwrap();
926            assert_eq!(actual_response, expected_response);
927        });
928    }
929
930    #[test]
931    fn klines_optional_params_success() {
932        TOKIO_SHARED_RT.block_on(async {
933            let client = MockMarketDataApiClient { force_error: false };
934
935            let params = KlinesParams::builder("ALPHA_175USDT".to_string(),KlinesIntervalEnum::Interval1s,).limit(500).start_time(1752642000000).end_time(1752645599999).build().unwrap();
936
937            let resp_json: Value = serde_json::from_str(r#"{"code":"000000","message":"","messageDetail":"","success":true,"data":[["1752642000000"]]}"#).unwrap_or_else(|_| serde_json::json!({}));
938            let expected_response : models::KlinesResponse = serde_json::from_value(resp_json.clone()).expect("should parse into models::KlinesResponse");
939
940            let resp = client.klines(params).await.expect("Expected a response");
941            let data_future = resp.data();
942            let actual_response = data_future.await.unwrap();
943            assert_eq!(actual_response, expected_response);
944        });
945    }
946
947    #[test]
948    fn klines_response_error() {
949        TOKIO_SHARED_RT.block_on(async {
950            let client = MockMarketDataApiClient { force_error: true };
951
952            let params =
953                KlinesParams::builder("ALPHA_175USDT".to_string(), KlinesIntervalEnum::Interval1s)
954                    .build()
955                    .unwrap();
956
957            match client.klines(params).await {
958                Ok(_) => panic!("Expected an error"),
959                Err(err) => {
960                    assert_eq!(err.to_string(), "Connector client error: ResponseError");
961                }
962            }
963        });
964    }
965
966    #[test]
967    fn ticker_required_params_success() {
968        TOKIO_SHARED_RT.block_on(async {
969            let client = MockMarketDataApiClient { force_error: false };
970
971            let params = TickerParams::builder("ALPHA_175USDT".to_string()).build().unwrap();
972
973            let resp_json: Value = serde_json::from_str(r#"{"code":"000000","message":"","messageDetail":"","data":{"symbol":"ALPHA_175USDT","priceChange":"-0.00007172","priceChangePercent":"-8.841","weightedAvgPrice":"0.00079608","lastPrice":"0.00073954","lastQty":"12600.43000000","openPrice":"0.00081126","highPrice":"0.00081126","lowPrice":"0.00073954","volume":"1204754.30000000","quoteVolume":"959.07729927","openTime":1768808100000,"closeTime":1768893244772,"firstId":93742,"lastId":93768,"count":38},"success":true}"#).unwrap_or_else(|_| serde_json::json!({}));
974            let expected_response : models::TickerResponse = serde_json::from_value(resp_json.clone()).expect("should parse into models::TickerResponse");
975
976            let resp = client.ticker(params).await.expect("Expected a response");
977            let data_future = resp.data();
978            let actual_response = data_future.await.unwrap();
979            assert_eq!(actual_response, expected_response);
980        });
981    }
982
983    #[test]
984    fn ticker_optional_params_success() {
985        TOKIO_SHARED_RT.block_on(async {
986            let client = MockMarketDataApiClient { force_error: false };
987
988            let params = TickerParams::builder("ALPHA_175USDT".to_string()).build().unwrap();
989
990            let resp_json: Value = serde_json::from_str(r#"{"code":"000000","message":"","messageDetail":"","data":{"symbol":"ALPHA_175USDT","priceChange":"-0.00007172","priceChangePercent":"-8.841","weightedAvgPrice":"0.00079608","lastPrice":"0.00073954","lastQty":"12600.43000000","openPrice":"0.00081126","highPrice":"0.00081126","lowPrice":"0.00073954","volume":"1204754.30000000","quoteVolume":"959.07729927","openTime":1768808100000,"closeTime":1768893244772,"firstId":93742,"lastId":93768,"count":38},"success":true}"#).unwrap_or_else(|_| serde_json::json!({}));
991            let expected_response : models::TickerResponse = serde_json::from_value(resp_json.clone()).expect("should parse into models::TickerResponse");
992
993            let resp = client.ticker(params).await.expect("Expected a response");
994            let data_future = resp.data();
995            let actual_response = data_future.await.unwrap();
996            assert_eq!(actual_response, expected_response);
997        });
998    }
999
1000    #[test]
1001    fn ticker_response_error() {
1002        TOKIO_SHARED_RT.block_on(async {
1003            let client = MockMarketDataApiClient { force_error: true };
1004
1005            let params = TickerParams::builder("ALPHA_175USDT".to_string())
1006                .build()
1007                .unwrap();
1008
1009            match client.ticker(params).await {
1010                Ok(_) => panic!("Expected an error"),
1011                Err(err) => {
1012                    assert_eq!(err.to_string(), "Connector client error: ResponseError");
1013                }
1014            }
1015        });
1016    }
1017
1018    #[test]
1019    fn token_list_required_params_success() {
1020        TOKIO_SHARED_RT.block_on(async {
1021            let client = MockMarketDataApiClient { force_error: false };
1022
1023
1024            let resp_json: Value = serde_json::from_str(r#"{"code":"000000","message":"","messageDetail":"","success":true,"data":[{"tokenId":"3F350C8B3621770A673159B7A19BC034","chainId":"56","chainIconUrl":"https://bin.bnbstatic.com/image/admin_mgs_image_upload/20250228/d0216ce4-a3e9-4bda-8937-4a6aa943ccf2.png","chainName":"BSC","contractAddress":"0xcf640fdf9b3d9e45cbd69fda91d7e22579c14444","name":"gorilla","symbol":"gorilla","iconUrl":"https://bin.bnbstatic.com/images/web3-data/public/token/logos/248b5406f88a4ee28913a29107875339.png","price":"0.00080595003978242023","percentChange24h":"-7.42","volume24h":"14847.711222633409558029675","marketCap":"805950.03978242","fdv":"805950.03978242","liquidity":"263192.72813121626034","totalSupply":"1000000000","circulatingSupply":"1000000000","holders":"7120","decimals":18,"listingCex":false,"hotTag":false,"cexCoinName":"","canTransfer":false,"denomination":1,"offline":false,"tradeDecimal":8,"alphaId":"ALPHA_175","offsell":false,"priceHigh24h":"0.00088873864475645879","priceLow24h":"0.0008020805165487083","count24h":"166","onlineTge":false,"onlineAirdrop":false,"score":1,"cexOffDisplay":false,"stockState":false,"listingTime":1746686700000,"mulPoint":1,"bnExclusiveState":false}]}"#).unwrap_or_else(|_| serde_json::json!({}));
1025            let expected_response : models::TokenListResponse = serde_json::from_value(resp_json.clone()).expect("should parse into models::TokenListResponse");
1026
1027            let resp = client.token_list().await.expect("Expected a response");
1028            let data_future = resp.data();
1029            let actual_response = data_future.await.unwrap();
1030            assert_eq!(actual_response, expected_response);
1031        });
1032    }
1033
1034    #[test]
1035    fn token_list_optional_params_success() {
1036        TOKIO_SHARED_RT.block_on(async {
1037            let client = MockMarketDataApiClient { force_error: false };
1038
1039
1040            let resp_json: Value = serde_json::from_str(r#"{"code":"000000","message":"","messageDetail":"","success":true,"data":[{"tokenId":"3F350C8B3621770A673159B7A19BC034","chainId":"56","chainIconUrl":"https://bin.bnbstatic.com/image/admin_mgs_image_upload/20250228/d0216ce4-a3e9-4bda-8937-4a6aa943ccf2.png","chainName":"BSC","contractAddress":"0xcf640fdf9b3d9e45cbd69fda91d7e22579c14444","name":"gorilla","symbol":"gorilla","iconUrl":"https://bin.bnbstatic.com/images/web3-data/public/token/logos/248b5406f88a4ee28913a29107875339.png","price":"0.00080595003978242023","percentChange24h":"-7.42","volume24h":"14847.711222633409558029675","marketCap":"805950.03978242","fdv":"805950.03978242","liquidity":"263192.72813121626034","totalSupply":"1000000000","circulatingSupply":"1000000000","holders":"7120","decimals":18,"listingCex":false,"hotTag":false,"cexCoinName":"","canTransfer":false,"denomination":1,"offline":false,"tradeDecimal":8,"alphaId":"ALPHA_175","offsell":false,"priceHigh24h":"0.00088873864475645879","priceLow24h":"0.0008020805165487083","count24h":"166","onlineTge":false,"onlineAirdrop":false,"score":1,"cexOffDisplay":false,"stockState":false,"listingTime":1746686700000,"mulPoint":1,"bnExclusiveState":false}]}"#).unwrap_or_else(|_| serde_json::json!({}));
1041            let expected_response : models::TokenListResponse = serde_json::from_value(resp_json.clone()).expect("should parse into models::TokenListResponse");
1042
1043            let resp = client.token_list().await.expect("Expected a response");
1044            let data_future = resp.data();
1045            let actual_response = data_future.await.unwrap();
1046            assert_eq!(actual_response, expected_response);
1047        });
1048    }
1049
1050    #[test]
1051    fn token_list_response_error() {
1052        TOKIO_SHARED_RT.block_on(async {
1053            let client = MockMarketDataApiClient { force_error: true };
1054
1055            match client.token_list().await {
1056                Ok(_) => panic!("Expected an error"),
1057                Err(err) => {
1058                    assert_eq!(err.to_string(), "Connector client error: ResponseError");
1059                }
1060            }
1061        });
1062    }
1063}