1use rust_decimal::Decimal;
7
8use super::client::BybitHttpClient;
9use super::models::{
10 AmendOrderRequest, CancelAllResult, CancelOrderRequest, CreateOrderRequest, ExecutionRecord,
11 InstrumentInfo, Kline, KlineResult, OrderBookResult, OrderRecord, OrderRef, Page,
12 PositionRecord, PublicTradeRecord, ServerTime, Ticker, WalletRecord,
13};
14use crate::error::BybitApiError;
15
16impl BybitHttpClient {
17 pub async fn get_server_time(&self) -> Result<ServerTime, BybitApiError> {
19 self.get_public("/v5/market/time", &[]).await
20 }
21
22 pub async fn get_instrument(
44 &self,
45 category: &str,
46 symbol: &str,
47 ) -> Result<InstrumentInfo, BybitApiError> {
48 let page: Page<InstrumentInfo> = self
49 .get_public(
50 "/v5/market/instruments-info",
51 &[("category", category.into()), ("symbol", symbol.into())],
52 )
53 .await?;
54 page.list
55 .into_iter()
56 .next()
57 .ok_or_else(|| BybitApiError::Api {
58 code: 10001,
59 message: format!("params error: symbol invalid ({symbol} is not in the answer)"),
60 })
61 }
62
63 pub async fn get_instruments_page(
65 &self,
66 category: &str,
67 limit: u32,
68 cursor: Option<&str>,
69 ) -> Result<Page<InstrumentInfo>, BybitApiError> {
70 let mut q = vec![
71 ("category", category.to_string()),
72 ("limit", limit.to_string()),
73 ];
74 if let Some(c) = cursor.filter(|c| !c.is_empty()) {
75 q.push(("cursor", c.to_string()));
76 }
77 self.get_public("/v5/market/instruments-info", &q).await
78 }
79
80 pub async fn get_order_book(
82 &self,
83 category: &str,
84 symbol: &str,
85 limit: Option<u32>,
86 ) -> Result<OrderBookView, BybitApiError> {
87 let mut q = vec![
88 ("category", category.to_string()),
89 ("symbol", symbol.to_string()),
90 ];
91 if let Some(l) = limit {
92 q.push(("limit", l.to_string()));
93 }
94 let r: OrderBookResult = self.get_public("/v5/market/orderbook", &q).await?;
95 Ok(OrderBookView {
96 symbol: r.symbol,
97 bids: r.bids.iter().map(|l| (l.0, l.1)).collect(),
98 asks: r.asks.iter().map(|l| (l.0, l.1)).collect(),
99 ts_ms: r.ts,
100 update_id: r.update_id,
101 })
102 }
103
104 pub async fn get_ticker(&self, category: &str, symbol: &str) -> Result<Ticker, BybitApiError> {
106 let page: Page<Ticker> = self
107 .get_public(
108 "/v5/market/tickers",
109 &[("category", category.into()), ("symbol", symbol.into())],
110 )
111 .await?;
112 page.list
113 .into_iter()
114 .next()
115 .ok_or_else(|| BybitApiError::Api {
116 code: 10001,
117 message: format!("params error: symbol invalid ({symbol} is not in the answer)"),
118 })
119 }
120
121 pub async fn get_recent_trades(
123 &self,
124 category: &str,
125 symbol: &str,
126 limit: u32,
127 ) -> Result<Vec<PublicTradeRecord>, BybitApiError> {
128 let page: Page<PublicTradeRecord> = self
129 .get_public(
130 "/v5/market/recent-trade",
131 &[
132 ("category", category.into()),
133 ("symbol", symbol.into()),
134 ("limit", limit.to_string()),
135 ],
136 )
137 .await?;
138 Ok(page.list)
139 }
140
141 pub async fn get_klines(
144 &self,
145 category: &str,
146 symbol: &str,
147 interval: &str,
148 start_ms: Option<i64>,
149 end_ms: Option<i64>,
150 limit: u32,
151 ) -> Result<Vec<Kline>, BybitApiError> {
152 let mut q = vec![
153 ("category", category.to_string()),
154 ("symbol", symbol.to_string()),
155 ("interval", interval.to_string()),
156 ("limit", limit.to_string()),
157 ];
158 if let Some(s) = start_ms {
159 q.push(("start", s.to_string()));
160 }
161 if let Some(e) = end_ms {
162 q.push(("end", e.to_string()));
163 }
164 let r: KlineResult = self.get_public("/v5/market/kline", &q).await?;
165 let mut out: Vec<Kline> = r
166 .list
167 .iter()
168 .filter_map(|row| Kline::from_row(row))
169 .collect();
170 out.sort_by_key(|k| k.start_ms);
171 Ok(out)
172 }
173
174 pub async fn create_order(&self, body: &CreateOrderRequest) -> Result<OrderRef, BybitApiError> {
176 self.post_command("/v5/order/create", body).await
177 }
178
179 pub async fn amend_order(&self, body: &AmendOrderRequest) -> Result<OrderRef, BybitApiError> {
181 self.post_command("/v5/order/amend", body).await
182 }
183
184 pub async fn cancel_order(&self, body: &CancelOrderRequest) -> Result<OrderRef, BybitApiError> {
186 self.post_command("/v5/order/cancel", body).await
187 }
188
189 pub async fn cancel_all(
191 &self,
192 category: &str,
193 symbol: Option<&str>,
194 settle_coin: &str,
195 ) -> Result<CancelAllResult, BybitApiError> {
196 let mut body = serde_json::json!({"category": category});
197 match symbol {
198 Some(s) => body["symbol"] = s.into(),
199 None => body["settleCoin"] = settle_coin.into(),
200 }
201 self.post_command("/v5/order/cancel-all", &body).await
202 }
203
204 #[allow(clippy::too_many_arguments)]
207 pub async fn get_open_orders(
208 &self,
209 category: &str,
210 symbol: Option<&str>,
211 settle_coin: Option<&str>,
212 order_id: Option<&str>,
213 order_link_id: Option<&str>,
214 limit: u32,
215 cursor: Option<&str>,
216 ) -> Result<Page<OrderRecord>, BybitApiError> {
217 let q = order_query(
218 category,
219 symbol,
220 settle_coin,
221 order_id,
222 order_link_id,
223 limit,
224 cursor,
225 );
226 self.get_private("/v5/order/realtime", &q).await
227 }
228
229 #[allow(clippy::too_many_arguments)]
231 pub async fn get_order_history(
232 &self,
233 category: &str,
234 symbol: Option<&str>,
235 order_id: Option<&str>,
236 order_link_id: Option<&str>,
237 limit: u32,
238 cursor: Option<&str>,
239 ) -> Result<Page<OrderRecord>, BybitApiError> {
240 let q = order_query(
241 category,
242 symbol,
243 None,
244 order_id,
245 order_link_id,
246 limit,
247 cursor,
248 );
249 self.get_private("/v5/order/history", &q).await
250 }
251
252 #[allow(clippy::too_many_arguments)]
254 pub async fn get_executions(
255 &self,
256 category: &str,
257 symbol: Option<&str>,
258 start_ms: Option<i64>,
259 end_ms: Option<i64>,
260 limit: u32,
261 cursor: Option<&str>,
262 ) -> Result<Page<ExecutionRecord>, BybitApiError> {
263 let mut q = vec![
264 ("category", category.to_string()),
265 ("limit", limit.to_string()),
266 ];
267 if let Some(s) = symbol {
268 q.push(("symbol", s.to_string()));
269 }
270 if let Some(s) = start_ms {
271 q.push(("startTime", s.to_string()));
272 }
273 if let Some(e) = end_ms {
274 q.push(("endTime", e.to_string()));
275 }
276 if let Some(c) = cursor.filter(|c| !c.is_empty()) {
277 q.push(("cursor", c.to_string()));
278 }
279 self.get_private("/v5/execution/list", &q).await
280 }
281
282 pub async fn get_positions(
284 &self,
285 category: &str,
286 settle_coin: &str,
287 limit: u32,
288 cursor: Option<&str>,
289 ) -> Result<Page<PositionRecord>, BybitApiError> {
290 let mut q = vec![
291 ("category", category.to_string()),
292 ("settleCoin", settle_coin.to_string()),
293 ("limit", limit.to_string()),
294 ];
295 if let Some(c) = cursor.filter(|c| !c.is_empty()) {
296 q.push(("cursor", c.to_string()));
297 }
298 self.get_private("/v5/position/list", &q).await
299 }
300
301 pub async fn get_wallet_balance(&self) -> Result<Vec<WalletRecord>, BybitApiError> {
303 #[derive(serde::Deserialize)]
304 struct R {
305 #[serde(default)]
306 list: Vec<WalletRecord>,
307 }
308 let r: R = self
309 .get_private(
310 "/v5/account/wallet-balance",
311 &[("accountType", "UNIFIED".to_string())],
312 )
313 .await?;
314 Ok(r.list)
315 }
316
317 pub async fn demo_apply_money(
320 &self,
321 coin: &str,
322 amount: Decimal,
323 ) -> Result<serde_json::Value, BybitApiError> {
324 let body = serde_json::json!({
325 "adjustType": 0,
326 "utaDemoApplyMoney": [{"coin": coin, "amountStr": amount.to_string()}],
327 });
328 self.post_command("/v5/account/demo-apply-money", &body)
329 .await
330 }
331}
332
333#[derive(Debug, Clone)]
335pub struct OrderBookView {
336 pub symbol: String,
338 pub bids: Vec<(Decimal, Decimal)>,
340 pub asks: Vec<(Decimal, Decimal)>,
342 pub ts_ms: i64,
344 pub update_id: i64,
346}
347
348fn order_query(
349 category: &str,
350 symbol: Option<&str>,
351 settle_coin: Option<&str>,
352 order_id: Option<&str>,
353 order_link_id: Option<&str>,
354 limit: u32,
355 cursor: Option<&str>,
356) -> Vec<(&'static str, String)> {
357 let mut q = vec![
358 ("category", category.to_string()),
359 ("limit", limit.to_string()),
360 ];
361 if let Some(s) = symbol {
362 q.push(("symbol", s.to_string()));
363 }
364 if let Some(s) = settle_coin {
365 q.push(("settleCoin", s.to_string()));
366 }
367 if let Some(s) = order_id {
368 q.push(("orderId", s.to_string()));
369 }
370 if let Some(s) = order_link_id {
371 q.push(("orderLinkId", s.to_string()));
372 }
373 if let Some(c) = cursor.filter(|c| !c.is_empty()) {
374 q.push(("cursor", c.to_string()));
375 }
376 q
377}