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