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}