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