longport/trade/
context.rs

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