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