longbridge/trade/
context.rs

1use std::sync::Arc;
2
3use longbridge_httpcli::{HttpClient, Json, Method};
4use longbridge_wscli::WsClientError;
5use rust_decimal::Decimal;
6use serde::{Deserialize, Serialize};
7use tokio::sync::{mpsc, oneshot};
8use tracing::{Subscriber, dispatcher, instrument::WithSubscriber};
9
10use crate::{
11    Config, Result, serde_utils,
12    trade::{
13        AccountBalance, CashFlow, EstimateMaxPurchaseQuantityOptions, Execution,
14        FundPositionsResponse, GetCashFlowOptions, GetFundPositionsOptions,
15        GetHistoryExecutionsOptions, GetHistoryOrdersOptions, GetStockPositionsOptions,
16        GetTodayExecutionsOptions, GetTodayOrdersOptions, MarginRatio, Order, OrderDetail,
17        PushEvent, ReplaceOrderOptions, StockPositionsResponse, SubmitOrderOptions, TopicType,
18        core::{Command, Core},
19    },
20};
21
22#[derive(Debug, Deserialize)]
23struct EmptyResponse {}
24
25/// Response for submit order request
26#[derive(Debug, Serialize, Deserialize)]
27pub struct SubmitOrderResponse {
28    /// Order id
29    pub order_id: String,
30}
31
32/// Response for estimate maximum purchase quantity
33#[derive(Debug, Serialize, Deserialize)]
34pub struct EstimateMaxPurchaseQuantityResponse {
35    /// Cash available quantity
36    #[serde(with = "serde_utils::decimal_empty_is_0")]
37    pub cash_max_qty: Decimal,
38    /// Margin available quantity
39    #[serde(with = "serde_utils::decimal_empty_is_0")]
40    pub margin_max_qty: Decimal,
41}
42
43struct InnerTradeContext {
44    command_tx: mpsc::UnboundedSender<Command>,
45    http_cli: HttpClient,
46    log_subscriber: Arc<dyn Subscriber + Send + Sync>,
47}
48
49impl Drop for InnerTradeContext {
50    fn drop(&mut self) {
51        dispatcher::with_default(&self.log_subscriber.clone().into(), || {
52            tracing::info!("trade context dropped");
53        });
54    }
55}
56
57/// Trade context
58#[derive(Clone)]
59pub struct TradeContext(Arc<InnerTradeContext>);
60
61impl TradeContext {
62    /// Create a `TradeContext`
63    pub async fn try_new(
64        config: Arc<Config>,
65    ) -> Result<(Self, mpsc::UnboundedReceiver<PushEvent>)> {
66        let log_subscriber = config.create_log_subscriber("trade");
67
68        dispatcher::with_default(&log_subscriber.clone().into(), || {
69            tracing::info!(language = ?config.language, "creating trade context");
70        });
71
72        let http_cli = config.create_http_client();
73        let (command_tx, command_rx) = mpsc::unbounded_channel();
74        let (push_tx, push_rx) = mpsc::unbounded_channel();
75        let core = Core::try_new(config, command_rx, push_tx)
76            .with_subscriber(log_subscriber.clone())
77            .await?;
78        tokio::spawn(core.run().with_subscriber(log_subscriber.clone()));
79
80        dispatcher::with_default(&log_subscriber.clone().into(), || {
81            tracing::info!("trade context created");
82        });
83
84        Ok((
85            TradeContext(Arc::new(InnerTradeContext {
86                http_cli,
87                command_tx,
88                log_subscriber,
89            })),
90            push_rx,
91        ))
92    }
93
94    /// Returns the log subscriber
95    #[inline]
96    pub fn log_subscriber(&self) -> Arc<dyn Subscriber + Send + Sync> {
97        self.0.log_subscriber.clone()
98    }
99
100    /// Subscribe
101    ///
102    /// Reference: <https://open.longbridge.com/en/docs/trade/trade-push#subscribe>
103    ///
104    /// # Examples
105    ///
106    /// ```no_run
107    /// use std::sync::Arc;
108    ///
109    /// use longbridge::{
110    ///     Config, decimal,
111    ///     oauth::OAuthBuilder,
112    ///     trade::{OrderSide, OrderType, SubmitOrderOptions, TimeInForceType, TradeContext},
113    /// };
114    ///
115    /// # tokio::runtime::Runtime::new().unwrap().block_on(async {
116    /// let oauth = OAuthBuilder::new("your-client-id")
117    ///     .build(|url| println!("Visit: {url}"))
118    ///     .await?;
119    /// let config = Arc::new(Config::from_oauth(oauth));
120    /// let (ctx, mut receiver) = TradeContext::try_new(config).await?;
121    ///
122    /// let opts = SubmitOrderOptions::new(
123    ///     "700.HK",
124    ///     OrderType::LO,
125    ///     OrderSide::Buy,
126    ///     decimal!(200),
127    ///     TimeInForceType::Day,
128    /// )
129    /// .submitted_price(decimal!(50i32));
130    /// let resp = ctx.submit_order(opts).await?;
131    /// println!("{:?}", resp);
132    ///
133    /// while let Some(event) = receiver.recv().await {
134    ///     println!("{:?}", event);
135    /// }
136    ///
137    /// # Ok::<_, Box<dyn std::error::Error>>(())
138    /// # });
139    /// ```
140    pub async fn subscribe<I>(&self, topics: I) -> Result<()>
141    where
142        I: IntoIterator<Item = TopicType>,
143    {
144        let (reply_tx, reply_rx) = oneshot::channel();
145        self.0
146            .command_tx
147            .send(Command::Subscribe {
148                topics: topics.into_iter().collect(),
149                reply_tx,
150            })
151            .map_err(|_| WsClientError::ClientClosed)?;
152        reply_rx.await.map_err(|_| WsClientError::ClientClosed)?
153    }
154
155    /// Unsubscribe
156    ///
157    /// Reference: <https://open.longbridge.com/en/docs/trade/trade-push#cancel-subscribe>
158    pub async fn unsubscribe<I>(&self, topics: I) -> Result<()>
159    where
160        I: IntoIterator<Item = TopicType>,
161    {
162        let (reply_tx, reply_rx) = oneshot::channel();
163        self.0
164            .command_tx
165            .send(Command::Unsubscribe {
166                topics: topics.into_iter().collect(),
167                reply_tx,
168            })
169            .map_err(|_| WsClientError::ClientClosed)?;
170        reply_rx.await.map_err(|_| WsClientError::ClientClosed)?
171    }
172
173    /// Get history executions
174    ///
175    /// Reference: <https://open.longbridge.com/en/docs/trade/execution/history_executions>
176    ///
177    /// # Examples
178    ///
179    /// ```no_run
180    /// use std::sync::Arc;
181    ///
182    /// use longbridge::{
183    ///     oauth::OAuthBuilder,
184    ///     trade::{GetHistoryExecutionsOptions, TradeContext},
185    ///     Config,
186    /// };
187    /// use time::macros::datetime;
188    ///
189    /// # tokio::runtime::Runtime::new().unwrap().block_on(async {
190    /// let oauth = OAuthBuilder::new("your-client-id")
191    ///     .build(|url| println!("Visit: {url}"))
192    ///     .await?;
193    /// let config = Arc::new(Config::from_oauth(oauth));
194    /// let (ctx, _) = TradeContext::try_new(config).await?;
195    ///
196    /// let opts = GetHistoryExecutionsOptions::new()
197    ///     .symbol("700.HK")
198    ///     .start_at(datetime!(2022-05-09 0:00 UTC))
199    ///     .end_at(datetime!(2022-05-12 0:00 UTC));
200    /// let resp = ctx.history_executions(opts).await?;
201    /// println!("{:?}", resp);
202    /// # Ok::<_, Box<dyn std::error::Error>>(())
203    /// # });
204    /// ```
205    pub async fn history_executions(
206        &self,
207        options: impl Into<Option<GetHistoryExecutionsOptions>>,
208    ) -> Result<Vec<Execution>> {
209        #[derive(Deserialize)]
210        struct Response {
211            trades: Vec<Execution>,
212        }
213
214        Ok(self
215            .0
216            .http_cli
217            .request(Method::GET, "/v1/trade/execution/history")
218            .query_params(options.into().unwrap_or_default())
219            .response::<Json<Response>>()
220            .send()
221            .with_subscriber(self.0.log_subscriber.clone())
222            .await?
223            .0
224            .trades)
225    }
226
227    /// Get today executions
228    ///
229    /// Reference: <https://open.longbridge.com/en/docs/trade/execution/today_executions>
230    ///
231    /// # Examples
232    ///
233    /// ```no_run
234    /// use std::sync::Arc;
235    ///
236    /// use longbridge::{
237    ///     Config,
238    ///     oauth::OAuthBuilder,
239    ///     trade::{GetTodayExecutionsOptions, TradeContext},
240    /// };
241    ///
242    /// # tokio::runtime::Runtime::new().unwrap().block_on(async {
243    /// let oauth = OAuthBuilder::new("your-client-id")
244    ///     .build(|url| println!("Visit: {url}"))
245    ///     .await?;
246    /// let config = Arc::new(Config::from_oauth(oauth));
247    /// let (ctx, _) = TradeContext::try_new(config).await?;
248    ///
249    /// let opts = GetTodayExecutionsOptions::new().symbol("700.HK");
250    /// let resp = ctx.today_executions(opts).await?;
251    /// println!("{:?}", resp);
252    /// # Ok::<_, Box<dyn std::error::Error>>(())
253    /// # });
254    /// ```
255    pub async fn today_executions(
256        &self,
257        options: impl Into<Option<GetTodayExecutionsOptions>>,
258    ) -> Result<Vec<Execution>> {
259        #[derive(Deserialize)]
260        struct Response {
261            trades: Vec<Execution>,
262        }
263
264        Ok(self
265            .0
266            .http_cli
267            .request(Method::GET, "/v1/trade/execution/today")
268            .query_params(options.into().unwrap_or_default())
269            .response::<Json<Response>>()
270            .send()
271            .with_subscriber(self.0.log_subscriber.clone())
272            .await?
273            .0
274            .trades)
275    }
276
277    /// Get history orders
278    ///
279    /// Reference: <https://open.longbridge.com/en/docs/trade/order/history_orders>
280    ///
281    /// # Examples
282    ///
283    /// ```no_run
284    /// use std::sync::Arc;
285    ///
286    /// use longbridge::{
287    ///     oauth::OAuthBuilder,
288    ///     trade::{GetHistoryOrdersOptions, OrderSide, OrderStatus, TradeContext},
289    ///     Config, Market,
290    /// };
291    /// use time::macros::datetime;
292    ///
293    /// # tokio::runtime::Runtime::new().unwrap().block_on(async {
294    /// let oauth = OAuthBuilder::new("your-client-id")
295    ///     .build(|url| println!("Visit: {url}"))
296    ///     .await?;
297    /// let config = Arc::new(Config::from_oauth(oauth));
298    /// let (ctx, _) = TradeContext::try_new(config).await?;
299    ///
300    /// let opts = GetHistoryOrdersOptions::new()
301    ///     .symbol("700.HK")
302    ///     .status([OrderStatus::Filled, OrderStatus::New])
303    ///     .side(OrderSide::Buy)
304    ///     .market(Market::HK)
305    ///     .start_at(datetime!(2022-05-09 0:00 UTC))
306    ///     .end_at(datetime!(2022-05-12 0:00 UTC));
307    /// let resp = ctx.history_orders(opts).await?;
308    /// println!("{:?}", resp);
309    /// # Ok::<_, Box<dyn std::error::Error>>(())
310    /// # });
311    /// ```
312    pub async fn history_orders(
313        &self,
314        options: impl Into<Option<GetHistoryOrdersOptions>>,
315    ) -> Result<Vec<Order>> {
316        #[derive(Deserialize)]
317        struct Response {
318            orders: Vec<Order>,
319        }
320
321        Ok(self
322            .0
323            .http_cli
324            .request(Method::GET, "/v1/trade/order/history")
325            .query_params(options.into().unwrap_or_default())
326            .response::<Json<Response>>()
327            .send()
328            .with_subscriber(self.0.log_subscriber.clone())
329            .await?
330            .0
331            .orders)
332    }
333
334    /// Get today orders
335    ///
336    /// Reference: <https://open.longbridge.com/en/docs/trade/order/today_orders>
337    ///
338    /// # Examples
339    ///
340    /// ```no_run
341    /// use std::sync::Arc;
342    ///
343    /// use longbridge::{
344    ///     Config, Market,
345    ///     oauth::OAuthBuilder,
346    ///     trade::{GetTodayOrdersOptions, OrderSide, OrderStatus, TradeContext},
347    /// };
348    ///
349    /// # tokio::runtime::Runtime::new().unwrap().block_on(async {
350    /// let oauth = OAuthBuilder::new("your-client-id")
351    ///     .build(|url| println!("Visit: {url}"))
352    ///     .await?;
353    /// let config = Arc::new(Config::from_oauth(oauth));
354    /// let (ctx, _) = TradeContext::try_new(config).await?;
355    ///
356    /// let opts = GetTodayOrdersOptions::new()
357    ///     .symbol("700.HK")
358    ///     .status([OrderStatus::Filled, OrderStatus::New])
359    ///     .side(OrderSide::Buy)
360    ///     .market(Market::HK);
361    /// let resp = ctx.today_orders(opts).await?;
362    /// println!("{:?}", resp);
363    /// # Ok::<_, Box<dyn std::error::Error>>(())
364    /// # });
365    /// ```
366    pub async fn today_orders(
367        &self,
368        options: impl Into<Option<GetTodayOrdersOptions>>,
369    ) -> Result<Vec<Order>> {
370        #[derive(Deserialize)]
371        struct Response {
372            orders: Vec<Order>,
373        }
374
375        Ok(self
376            .0
377            .http_cli
378            .request(Method::GET, "/v1/trade/order/today")
379            .query_params(options.into().unwrap_or_default())
380            .response::<Json<Response>>()
381            .send()
382            .with_subscriber(self.0.log_subscriber.clone())
383            .await?
384            .0
385            .orders)
386    }
387
388    /// Replace order
389    ///
390    /// Reference: <https://open.longbridge.com/en/docs/trade/order/replace>
391    ///
392    /// # Examples
393    ///
394    /// ```no_run
395    /// use std::sync::Arc;
396    ///
397    /// use longbridge::{
398    ///     Config, decimal,
399    ///     oauth::OAuthBuilder,
400    ///     trade::{ReplaceOrderOptions, TradeContext},
401    /// };
402    ///
403    /// # tokio::runtime::Runtime::new().unwrap().block_on(async {
404    /// let oauth = OAuthBuilder::new("your-client-id")
405    ///     .build(|url| println!("Visit: {url}"))
406    ///     .await?;
407    /// let config = Arc::new(Config::from_oauth(oauth));
408    /// let (ctx, _) = TradeContext::try_new(config).await?;
409    ///
410    /// let opts =
411    ///     ReplaceOrderOptions::new("709043056541253632", decimal!(100)).price(decimal!(300i32));
412    /// let resp = ctx.replace_order(opts).await?;
413    /// println!("{:?}", resp);
414    /// # Ok::<_, Box<dyn std::error::Error>>(())
415    /// # });
416    /// ```
417    pub async fn replace_order(&self, options: ReplaceOrderOptions) -> Result<()> {
418        Ok(self
419            .0
420            .http_cli
421            .request(Method::PUT, "/v1/trade/order")
422            .body(Json(options))
423            .response::<Json<EmptyResponse>>()
424            .send()
425            .with_subscriber(self.0.log_subscriber.clone())
426            .await
427            .map(|_| ())?)
428    }
429
430    /// Submit order
431    ///
432    /// Reference: <https://open.longbridge.com/en/docs/trade/order/submit>
433    ///
434    /// # Examples
435    ///
436    /// ```no_run
437    /// use std::sync::Arc;
438    ///
439    /// use longbridge::{
440    ///     Config, decimal,
441    ///     oauth::OAuthBuilder,
442    ///     trade::{OrderSide, OrderType, SubmitOrderOptions, TimeInForceType, TradeContext},
443    /// };
444    ///
445    /// # tokio::runtime::Runtime::new().unwrap().block_on(async {
446    /// let oauth = OAuthBuilder::new("your-client-id")
447    ///     .build(|url| println!("Visit: {url}"))
448    ///     .await?;
449    /// let config = Arc::new(Config::from_oauth(oauth));
450    /// let (ctx, _) = TradeContext::try_new(config).await?;
451    ///
452    /// let opts = SubmitOrderOptions::new(
453    ///     "700.HK",
454    ///     OrderType::LO,
455    ///     OrderSide::Buy,
456    ///     decimal!(200),
457    ///     TimeInForceType::Day,
458    /// )
459    /// .submitted_price(decimal!(50i32));
460    /// let resp = ctx.submit_order(opts).await?;
461    /// println!("{:?}", resp);
462    /// # Ok::<_, Box<dyn std::error::Error>>(())
463    /// # });
464    /// ```
465    pub async fn submit_order(&self, options: SubmitOrderOptions) -> Result<SubmitOrderResponse> {
466        let resp: SubmitOrderResponse = self
467            .0
468            .http_cli
469            .request(Method::POST, "/v1/trade/order")
470            .body(Json(options))
471            .response::<Json<_>>()
472            .send()
473            .with_subscriber(self.0.log_subscriber.clone())
474            .await?
475            .0;
476        _ = self.0.command_tx.send(Command::SubmittedOrder {
477            order_id: resp.order_id.clone(),
478        });
479        Ok(resp)
480    }
481
482    /// Cancel order
483    ///
484    /// Reference: <https://open.longbridge.com/en/docs/trade/order/withdraw>
485    ///
486    /// # Examples
487    ///
488    /// ```no_run
489    /// use std::sync::Arc;
490    ///
491    /// use longbridge::{Config, oauth::OAuthBuilder, trade::TradeContext};
492    ///
493    /// # tokio::runtime::Runtime::new().unwrap().block_on(async {
494    /// let oauth = OAuthBuilder::new("your-client-id")
495    ///     .build(|url| println!("Visit: {url}"))
496    ///     .await?;
497    /// let config = Arc::new(Config::from_oauth(oauth));
498    /// let (ctx, _) = TradeContext::try_new(config).await?;
499    ///
500    /// ctx.cancel_order("709043056541253632").await?;
501    /// # Ok::<_, Box<dyn std::error::Error>>(())
502    /// # });
503    /// ```
504    pub async fn cancel_order(&self, order_id: impl Into<String>) -> Result<()> {
505        #[derive(Debug, Serialize)]
506        struct Request {
507            order_id: String,
508        }
509
510        Ok(self
511            .0
512            .http_cli
513            .request(Method::DELETE, "/v1/trade/order")
514            .response::<Json<EmptyResponse>>()
515            .query_params(Request {
516                order_id: order_id.into(),
517            })
518            .send()
519            .with_subscriber(self.0.log_subscriber.clone())
520            .await
521            .map(|_| ())?)
522    }
523
524    /// Get account balance
525    ///
526    /// Reference: <https://open.longbridge.com/en/docs/trade/asset/account>
527    ///
528    /// # Examples
529    ///
530    /// ```no_run
531    /// use std::sync::Arc;
532    ///
533    /// use longbridge::{Config, oauth::OAuthBuilder, trade::TradeContext};
534    ///
535    /// # tokio::runtime::Runtime::new().unwrap().block_on(async {
536    /// let oauth = OAuthBuilder::new("your-client-id")
537    ///     .build(|url| println!("Visit: {url}"))
538    ///     .await?;
539    /// let config = Arc::new(Config::from_oauth(oauth));
540    /// let (ctx, _) = TradeContext::try_new(config).await?;
541    ///
542    /// let resp = ctx.account_balance(None).await?;
543    /// println!("{:?}", resp);
544    /// # Ok::<_, Box<dyn std::error::Error>>(())
545    /// # });
546    /// ```
547    pub async fn account_balance(&self, currency: Option<&str>) -> Result<Vec<AccountBalance>> {
548        #[derive(Debug, Serialize)]
549        struct Request<'a> {
550            currency: Option<&'a str>,
551        }
552
553        #[derive(Debug, Deserialize)]
554        struct Response {
555            list: Vec<AccountBalance>,
556        }
557
558        Ok(self
559            .0
560            .http_cli
561            .request(Method::GET, "/v1/asset/account")
562            .query_params(Request { currency })
563            .response::<Json<Response>>()
564            .send()
565            .with_subscriber(self.0.log_subscriber.clone())
566            .await?
567            .0
568            .list)
569    }
570
571    /// Get cash flow
572    ///
573    /// Reference: <https://open.longbridge.com/en/docs/trade/asset/cashflow>
574    ///
575    /// # Examples
576    ///
577    /// ```no_run
578    /// use std::sync::Arc;
579    ///
580    /// use longbridge::{
581    ///     oauth::OAuthBuilder,
582    ///     trade::{GetCashFlowOptions, TradeContext},
583    ///     Config,
584    /// };
585    /// use time::macros::datetime;
586    ///
587    /// # tokio::runtime::Runtime::new().unwrap().block_on(async {
588    /// let oauth = OAuthBuilder::new("your-client-id")
589    ///     .build(|url| println!("Visit: {url}"))
590    ///     .await?;
591    /// let config = Arc::new(Config::from_oauth(oauth));
592    /// let (ctx, _) = TradeContext::try_new(config).await?;
593    ///
594    /// let opts = GetCashFlowOptions::new(datetime!(2022-05-09 0:00 UTC), datetime!(2022-05-12 0:00 UTC));
595    /// let resp = ctx.cash_flow(opts).await?;
596    /// println!("{:?}", resp);
597    /// # Ok::<_, Box<dyn std::error::Error>>(())
598    /// # });
599    /// ```
600    pub async fn cash_flow(&self, options: GetCashFlowOptions) -> Result<Vec<CashFlow>> {
601        #[derive(Debug, Deserialize)]
602        struct Response {
603            list: Vec<CashFlow>,
604        }
605
606        Ok(self
607            .0
608            .http_cli
609            .request(Method::GET, "/v1/asset/cashflow")
610            .query_params(options)
611            .response::<Json<Response>>()
612            .send()
613            .with_subscriber(self.0.log_subscriber.clone())
614            .await?
615            .0
616            .list)
617    }
618
619    /// Get fund positions
620    ///
621    /// Reference: <https://open.longbridge.com/en/docs/trade/asset/fund>
622    ///
623    /// # Examples
624    ///
625    /// ```no_run
626    /// use std::sync::Arc;
627    ///
628    /// use longbridge::{Config, oauth::OAuthBuilder, trade::TradeContext};
629    ///
630    /// # tokio::runtime::Runtime::new().unwrap().block_on(async {
631    /// let oauth = OAuthBuilder::new("your-client-id")
632    ///     .build(|url| println!("Visit: {url}"))
633    ///     .await?;
634    /// let config = Arc::new(Config::from_oauth(oauth));
635    /// let (ctx, _) = TradeContext::try_new(config).await?;
636    ///
637    /// let resp = ctx.fund_positions(None).await?;
638    /// println!("{:?}", resp);
639    /// # Ok::<_, Box<dyn std::error::Error>>(())
640    /// # });
641    /// ```
642    pub async fn fund_positions(
643        &self,
644        opts: impl Into<Option<GetFundPositionsOptions>>,
645    ) -> Result<FundPositionsResponse> {
646        Ok(self
647            .0
648            .http_cli
649            .request(Method::GET, "/v1/asset/fund")
650            .query_params(opts.into().unwrap_or_default())
651            .response::<Json<FundPositionsResponse>>()
652            .send()
653            .with_subscriber(self.0.log_subscriber.clone())
654            .await?
655            .0)
656    }
657
658    /// Get stock positions
659    ///
660    /// Reference: <https://open.longbridge.com/en/docs/trade/asset/stock>
661    ///
662    /// # Examples
663    ///
664    /// ```no_run
665    /// use std::sync::Arc;
666    ///
667    /// use longbridge::{Config, oauth::OAuthBuilder, trade::TradeContext};
668    ///
669    /// # tokio::runtime::Runtime::new().unwrap().block_on(async {
670    /// let oauth = OAuthBuilder::new("your-client-id")
671    ///     .build(|url| println!("Visit: {url}"))
672    ///     .await?;
673    /// let config = Arc::new(Config::from_oauth(oauth));
674    /// let (ctx, _) = TradeContext::try_new(config).await?;
675    ///
676    /// let resp = ctx.stock_positions(None).await?;
677    /// println!("{:?}", resp);
678    /// # Ok::<_, Box<dyn std::error::Error>>(())
679    /// # });
680    /// ```
681    pub async fn stock_positions(
682        &self,
683        opts: impl Into<Option<GetStockPositionsOptions>>,
684    ) -> Result<StockPositionsResponse> {
685        Ok(self
686            .0
687            .http_cli
688            .request(Method::GET, "/v1/asset/stock")
689            .query_params(opts.into().unwrap_or_default())
690            .response::<Json<StockPositionsResponse>>()
691            .send()
692            .with_subscriber(self.0.log_subscriber.clone())
693            .await?
694            .0)
695    }
696
697    /// Get margin ratio
698    ///
699    /// Reference: <https://open.longbridge.com/en/docs/trade/asset/margin_ratio>
700    ///
701    /// # Examples
702    ///
703    /// ```no_run
704    /// use std::sync::Arc;
705    ///
706    /// use longbridge::{Config, oauth::OAuthBuilder, trade::TradeContext};
707    ///
708    /// # tokio::runtime::Runtime::new().unwrap().block_on(async {
709    /// let oauth = OAuthBuilder::new("your-client-id")
710    ///     .build(|url| println!("Visit: {url}"))
711    ///     .await?;
712    /// let config = Arc::new(Config::from_oauth(oauth));
713    /// let (ctx, _) = TradeContext::try_new(config).await?;
714    ///
715    /// let resp = ctx.margin_ratio("700.HK").await?;
716    /// println!("{:?}", resp);
717    /// # Ok::<_, Box<dyn std::error::Error>>(())
718    /// # });
719    /// ```
720    pub async fn margin_ratio(&self, symbol: impl Into<String>) -> Result<MarginRatio> {
721        #[derive(Debug, Serialize)]
722        struct Request {
723            symbol: String,
724        }
725
726        Ok(self
727            .0
728            .http_cli
729            .request(Method::GET, "/v1/risk/margin-ratio")
730            .query_params(Request {
731                symbol: symbol.into(),
732            })
733            .response::<Json<MarginRatio>>()
734            .send()
735            .with_subscriber(self.0.log_subscriber.clone())
736            .await?
737            .0)
738    }
739
740    /// Get order detail
741    ///
742    /// Reference: <https://open.longbridge.com/en/docs/trade/order/order_detail>
743    ///
744    /// # Examples
745    ///
746    /// ```no_run
747    /// use std::sync::Arc;
748    ///
749    /// use longbridge::{
750    ///     Config, Market,
751    ///     oauth::OAuthBuilder,
752    ///     trade::{GetHistoryOrdersOptions, OrderSide, OrderStatus, TradeContext},
753    /// };
754    /// use time::macros::datetime;
755    ///
756    /// # tokio::runtime::Runtime::new().unwrap().block_on(async {
757    /// let oauth = OAuthBuilder::new("your-client-id")
758    ///     .build(|url| println!("Visit: {url}"))
759    ///     .await?;
760    /// let config = Arc::new(Config::from_oauth(oauth));
761    /// let (ctx, _) = TradeContext::try_new(config).await?;
762    ///
763    /// let resp = ctx.order_detail("701276261045858304").await?;
764    /// println!("{:?}", resp);
765    /// # Ok::<_, Box<dyn std::error::Error>>(())
766    /// # });
767    /// ```
768    pub async fn order_detail(&self, order_id: impl Into<String>) -> Result<OrderDetail> {
769        #[derive(Debug, Serialize)]
770        struct Request {
771            order_id: String,
772        }
773
774        Ok(self
775            .0
776            .http_cli
777            .request(Method::GET, "/v1/trade/order")
778            .response::<Json<OrderDetail>>()
779            .query_params(Request {
780                order_id: order_id.into(),
781            })
782            .send()
783            .with_subscriber(self.0.log_subscriber.clone())
784            .await?
785            .0)
786    }
787
788    /// Estimating the maximum purchase quantity for Hong Kong and US stocks,
789    /// warrants, and options
790    ///
791    ///
792    /// Reference: <https://open.longbridge.com/en/docs/trade/order/estimate_available_buy_limit>
793    ///
794    /// # Examples
795    ///
796    /// ```no_run
797    /// use std::sync::Arc;
798    ///
799    /// use longbridge::{
800    ///     Config,
801    ///     oauth::OAuthBuilder,
802    ///     trade::{EstimateMaxPurchaseQuantityOptions, OrderSide, OrderType, TradeContext},
803    /// };
804    /// use time::macros::datetime;
805    ///
806    /// # tokio::runtime::Runtime::new().unwrap().block_on(async {
807    /// let oauth = OAuthBuilder::new("your-client-id")
808    ///     .build(|url| println!("Visit: {url}"))
809    ///     .await?;
810    /// let config = Arc::new(Config::from_oauth(oauth));
811    /// let (ctx, _) = TradeContext::try_new(config).await?;
812    ///
813    /// let resp = ctx
814    ///     .estimate_max_purchase_quantity(EstimateMaxPurchaseQuantityOptions::new(
815    ///         "700.HK",
816    ///         OrderType::LO,
817    ///         OrderSide::Buy,
818    ///     ))
819    ///     .await?;
820    /// println!("{:?}", resp);
821    /// # Ok::<_, Box<dyn std::error::Error>>(())
822    /// # });
823    /// ```
824    pub async fn estimate_max_purchase_quantity(
825        &self,
826        opts: EstimateMaxPurchaseQuantityOptions,
827    ) -> Result<EstimateMaxPurchaseQuantityResponse> {
828        Ok(self
829            .0
830            .http_cli
831            .request(Method::GET, "/v1/trade/estimate/buy_limit")
832            .query_params(opts)
833            .response::<Json<EstimateMaxPurchaseQuantityResponse>>()
834            .send()
835            .with_subscriber(self.0.log_subscriber.clone())
836            .await?
837            .0)
838    }
839}