Skip to main content

longbridge/grid/
context.rs

1use std::sync::Arc;
2
3use longbridge_httpcli::{HttpClient, Json, Method};
4use serde::{Deserialize, Serialize};
5use tracing::{Subscriber, dispatcher, instrument::WithSubscriber};
6
7use crate::{
8    Config, Result,
9    grid::{
10        GetGridOrderDetailOptions, GetGridOrdersByIdsOptions, GetGridOrdersOptions,
11        GetGridTriggerHistoryOptions, GridOrder, GridOrderDetail, GridSymbolInfo,
12        ReplaceGridOrderOptions, SubmitGridOrderOptions, TriggerOrder,
13    },
14};
15
16#[derive(Debug, Deserialize)]
17struct EmptyResponse {}
18
19/// Response for submit grid trading order request
20#[derive(Debug, Serialize, Deserialize)]
21pub struct SubmitGridOrderResponse {
22    /// Grid master order id
23    pub order_id: String,
24}
25
26/// Response for get grid trading orders (list) request
27#[derive(Debug, Deserialize)]
28pub struct GridOrdersResponse {
29    /// Grid orders
30    #[serde(default)]
31    pub grid_order: Vec<GridOrder>,
32    /// Whether there are more pages
33    #[serde(default)]
34    pub has_more: bool,
35}
36
37/// Response for get grid trading trigger history request
38#[derive(Debug, Deserialize)]
39pub struct GridTriggerHistoryResponse {
40    /// Trigger history entries
41    #[serde(default)]
42    pub trigger_orders: Vec<TriggerOrder>,
43    /// Whether there are more pages
44    #[serde(default)]
45    pub has_more: bool,
46}
47
48struct InnerGridContext {
49    http_cli: HttpClient,
50    log_subscriber: Arc<dyn Subscriber + Send + Sync>,
51}
52
53impl Drop for InnerGridContext {
54    fn drop(&mut self) {
55        dispatcher::with_default(&self.log_subscriber.clone().into(), || {
56            tracing::info!("grid context dropped");
57        });
58    }
59}
60
61/// Grid trading management context.
62#[derive(Clone)]
63pub struct GridContext(Arc<InnerGridContext>);
64
65impl GridContext {
66    /// Create a [`GridContext`]
67    pub fn new(config: Arc<Config>) -> Self {
68        let log_subscriber = config.create_log_subscriber("grid");
69        dispatcher::with_default(&log_subscriber.clone().into(), || {
70            tracing::info!(language = ?config.language, "creating grid context");
71        });
72        let ctx = Self(Arc::new(InnerGridContext {
73            http_cli: config.create_http_client(),
74            log_subscriber,
75        }));
76        dispatcher::with_default(&ctx.0.log_subscriber.clone().into(), || {
77            tracing::info!("grid context created");
78        });
79        ctx
80    }
81
82    /// Returns the log subscriber
83    #[inline]
84    pub fn log_subscriber(&self) -> Arc<dyn Subscriber + Send + Sync> {
85        self.0.log_subscriber.clone()
86    }
87
88    /// Submit a grid trading order
89    pub async fn submit(&self, options: SubmitGridOrderOptions) -> Result<SubmitGridOrderResponse> {
90        Ok(self
91            .0
92            .http_cli
93            .request(Method::POST, "/v1/gridtrading/submit")
94            .body(Json(options))
95            .response::<Json<SubmitGridOrderResponse>>()
96            .send()
97            .with_subscriber(self.0.log_subscriber.clone())
98            .await?
99            .0)
100    }
101
102    /// Replace (modify) a grid trading order
103    pub async fn replace(&self, options: ReplaceGridOrderOptions) -> Result<()> {
104        self.0
105            .http_cli
106            .request(Method::POST, "/v1/gridtrading/replace")
107            .body(Json(options))
108            .response::<Json<EmptyResponse>>()
109            .send()
110            .with_subscriber(self.0.log_subscriber.clone())
111            .await?;
112        Ok(())
113    }
114
115    /// Get grid trading orders (paged list)
116    pub async fn list(
117        &self,
118        options: impl Into<Option<GetGridOrdersOptions>>,
119    ) -> Result<GridOrdersResponse> {
120        Ok(self
121            .0
122            .http_cli
123            .request(Method::GET, "/v1/gridtrading/list")
124            .query_params(options.into().unwrap_or_default())
125            .response::<Json<GridOrdersResponse>>()
126            .send()
127            .with_subscriber(self.0.log_subscriber.clone())
128            .await?
129            .0)
130    }
131
132    /// Query grid trading orders by IDs
133    pub async fn list_by_ids(&self, options: GetGridOrdersByIdsOptions) -> Result<Vec<GridOrder>> {
134        #[derive(Deserialize)]
135        struct Response {
136            #[serde(default)]
137            grid_order: Vec<GridOrder>,
138        }
139
140        Ok(self
141            .0
142            .http_cli
143            .request(Method::POST, "/v1/gridtrading/list")
144            .body(Json(options))
145            .response::<Json<Response>>()
146            .send()
147            .with_subscriber(self.0.log_subscriber.clone())
148            .await?
149            .0
150            .grid_order)
151    }
152
153    /// Get grid trading order detail (and paged history)
154    pub async fn detail(&self, options: GetGridOrderDetailOptions) -> Result<GridOrderDetail> {
155        Ok(self
156            .0
157            .http_cli
158            .request(Method::GET, "/v1/gridtrading/detail")
159            .query_params(options)
160            .response::<Json<GridOrderDetail>>()
161            .send()
162            .with_subscriber(self.0.log_subscriber.clone())
163            .await?
164            .0)
165    }
166
167    /// Get grid trading trigger history
168    pub async fn trigger_history(
169        &self,
170        options: GetGridTriggerHistoryOptions,
171    ) -> Result<GridTriggerHistoryResponse> {
172        Ok(self
173            .0
174            .http_cli
175            .request(Method::GET, "/v1/gridtrading/trigger_history_list")
176            .query_params(options)
177            .response::<Json<GridTriggerHistoryResponse>>()
178            .send()
179            .with_subscriber(self.0.log_subscriber.clone())
180            .await?
181            .0)
182    }
183
184    /// Cancel a grid trading order
185    pub async fn cancel(&self, order_id: impl Into<String>) -> Result<()> {
186        self.grid_action("/v1/gridtrading/cancel", order_id).await
187    }
188
189    /// Suspend a grid trading order
190    pub async fn suspend(&self, order_id: impl Into<String>) -> Result<()> {
191        self.grid_action("/v1/gridtrading/suspend", order_id).await
192    }
193
194    /// Restart a grid trading order
195    pub async fn restart(&self, order_id: impl Into<String>) -> Result<()> {
196        self.grid_action("/v1/gridtrading/restart", order_id).await
197    }
198
199    /// Shared body for the cancel / suspend / restart grid actions.
200    async fn grid_action(&self, path: &'static str, order_id: impl Into<String>) -> Result<()> {
201        #[derive(Debug, Serialize)]
202        struct Body {
203            order_id: String,
204        }
205
206        self.0
207            .http_cli
208            .request(Method::POST, path)
209            .body(Json(Body {
210                order_id: order_id.into(),
211            }))
212            .response::<Json<EmptyResponse>>()
213            .send()
214            .with_subscriber(self.0.log_subscriber.clone())
215            .await?;
216        Ok(())
217    }
218
219    /// Get the security (symbol) info used to build a grid order (lot size,
220    /// authorization flag, settlement currency, etc.).
221    pub async fn symbol_info(&self, symbol: impl Into<String>) -> Result<GridSymbolInfo> {
222        #[derive(Debug, Serialize)]
223        struct Query {
224            symbol: String,
225        }
226
227        Ok(self
228            .0
229            .http_cli
230            .request(Method::GET, "/v1/orders/info")
231            .query_params(Query {
232                symbol: symbol.into(),
233            })
234            .response::<Json<GridSymbolInfo>>()
235            .send()
236            .with_subscriber(self.0.log_subscriber.clone())
237            .await?
238            .0)
239    }
240}