1use serde::{Deserialize, Serialize};
15
16#[derive(Debug, Clone, Serialize, Deserialize)]
18pub struct ServerTime {
19 #[serde(rename = "timeSecond")]
20 pub time_second: String,
21 #[serde(rename = "timeNano")]
22 pub time_nano: String,
23}
24
25#[derive(Debug, Clone, Serialize, Deserialize)]
27pub struct EmptyResult;
28
29#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq)]
31pub enum Category {
32 #[serde(rename = "linear")]
33 Linear,
34 #[serde(rename = "inverse")]
35 Inverse,
36 #[serde(rename = "spot")]
37 Spot,
38 #[serde(rename = "option")]
39 Option,
40}
41
42#[derive(Debug, Clone, Serialize, Deserialize)]
44pub struct ApiResponse<T> {
45 #[serde(rename = "retCode")]
46 pub ret_code: i32,
47 #[serde(rename = "retMsg")]
48 pub ret_msg: String,
49 pub result: T,
50 #[serde(rename = "retExtInfo", default)]
51 pub ret_ext_info: serde_json::Value,
52 pub time: i64,
53}
54
55#[derive(Debug, Clone, Serialize, Deserialize)]
56pub struct OrderBook {
57 pub b: Vec<(String, String)>,
58 pub a: Vec<(String, String)>,
59 pub ts: i64,
60 pub u: i64,
61}
62
63#[derive(Debug, Clone, Serialize, Deserialize)]
64pub struct InstrumentInfo {
65 pub symbol: String,
66 #[serde(rename = "contractType")]
67 pub contract_type: String,
68 pub status: String,
69 #[serde(rename = "baseCoin")]
70 pub base_coin: String,
71 #[serde(rename = "quoteCoin")]
72 pub quote_coin: String,
73 #[serde(rename = "settleCoin")]
74 pub settle_coin: String,
75 #[serde(rename = "priceScale")]
76 pub price_scale: String,
77}
78
79#[derive(Debug, Clone, Serialize, Deserialize)]
80pub struct Ticker {
81 pub symbol: String,
82 #[serde(rename = "lastPrice")]
83 pub last_price: String,
84 #[serde(rename = "indexPrice")]
85 pub index_price: String,
86 #[serde(rename = "markPrice")]
87 pub mark_price: String,
88 #[serde(rename = "bid1Price")]
89 pub bid1_price: String,
90 #[serde(rename = "bid1Size")]
91 pub bid1_size: String,
92 #[serde(rename = "ask1Price")]
93 pub ask1_price: String,
94 #[serde(rename = "ask1Size")]
95 pub ask1_size: String,
96}
97
98#[derive(Debug, Clone, Serialize, Deserialize)]
100pub struct TickerList {
101 pub list: Vec<Ticker>,
102 pub next_page_cursor: Option<String>,
103}
104
105#[derive(Debug, Clone, Serialize, Deserialize)]
107pub struct InstrumentList {
108 pub list: Vec<InstrumentInfo>,
109 pub next_page_cursor: Option<String>,
110}
111
112#[derive(Debug, Clone, Serialize, Deserialize)]
114pub struct WalletBalance {
115 pub list: Vec<AccountBalance>,
116}
117
118#[derive(Debug, Clone, Serialize, Deserialize)]
119pub struct AccountBalance {
120 #[serde(rename = "accountType")]
121 pub account_type: String,
122 #[serde(rename = "accountIMRate")]
123 pub account_im_rate: String,
124 #[serde(rename = "accountMMRate")]
125 pub account_mm_rate: String,
126 #[serde(rename = "totalEquity")]
127 pub total_equity: String,
128 #[serde(rename = "totalWalletBalance")]
129 pub total_wallet_balance: String,
130 #[serde(rename = "totalMarginBalance")]
131 pub total_margin_balance: String,
132 #[serde(rename = "totalAvailableBalance")]
133 pub total_available_balance: String,
134 #[serde(rename = "totalPerpUPL")]
135 pub total_perp_upl: String,
136 #[serde(rename = "totalInitialMargin")]
137 pub total_initial_margin: String,
138 #[serde(rename = "totalMaintenanceMargin")]
139 pub total_maintenance_margin: String,
140 pub coin: Vec<CoinBalance>,
141}
142
143#[derive(Debug, Clone, Serialize, Deserialize)]
144pub struct CoinBalance {
145 pub coin: String,
146 pub wallet_balance: String,
147 #[serde(rename = "transferBalance")]
148 pub transfer_balance: String,
149}
150
151#[derive(Debug, Clone, Serialize, Deserialize)]
153pub struct PositionList {
154 pub list: Vec<Position>,
155 pub category: String,
156 pub next_page_cursor: Option<String>,
157}
158
159#[derive(Debug, Clone, Serialize, Deserialize)]
160pub struct Position {
161 pub symbol: String,
162 #[serde(rename = "positionIdx")]
163 pub position_idx: u64,
164 #[serde(rename = "positionStatus")]
165 pub position_status: String,
166 pub side: String,
167 pub size: String,
168 #[serde(rename = "positionValue")]
169 pub position_value: String,
170 #[serde(rename = "unrealisedPnl")]
171 pub unrealised_pnl: String,
172}
173
174#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq)]
176pub enum Side {
177 #[serde(rename = "Buy")]
178 Buy,
179 #[serde(rename = "Sell")]
180 Sell,
181}
182
183#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq)]
185pub enum OrderType {
186 #[serde(rename = "Market")]
187 Market,
188 #[serde(rename = "Limit")]
189 Limit,
190}
191
192#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq)]
194pub enum TimeInForce {
195 #[serde(rename = "GTC")]
196 GTC,
197 #[serde(rename = "IOC")]
198 IOC,
199 #[serde(rename = "FOK")]
200 FOK,
201 #[serde(rename = "PostOnly")]
202 PostOnly,
203 #[serde(rename = "RPI")]
204 RPI,
205}
206
207#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq)]
209pub enum OrderStatus {
210 #[serde(rename = "New")]
211 New,
212 #[serde(rename = "PartiallyFilled")]
213 PartiallyFilled,
214 #[serde(rename = "Filled")]
215 Filled,
216 #[serde(rename = "Cancelled")]
217 Cancelled,
218 #[serde(rename = "Rejected")]
219 Rejected,
220}
221
222#[derive(Debug, Clone, Serialize, Deserialize)]
224pub struct OrderList {
225 pub list: Vec<Order>,
226 pub next_page_cursor: Option<String>,
227 pub category: String,
228}
229
230#[derive(Debug, Clone, Serialize, Deserialize)]
231pub struct Order {
232 pub order_id: String,
233 pub order_link_id: String,
234 pub symbol: String,
235 pub side: String,
236 pub order_type: String,
237 pub price: String,
238 pub qty: String,
239 pub time_in_force: String,
240 pub create_type: String,
241 pub cancel_type: String,
242 pub status: String,
243 pub leaves_qty: String,
244 pub cum_exec_qty: String,
245 pub avg_price: String,
246 pub created_time: String,
247 pub updated_time: String,
248 #[serde(rename = "positionIdx")]
249 pub position_idx: u64,
250 #[serde(rename = "triggerPrice")]
251 pub trigger_price: Option<String>,
252 #[serde(rename = "takeProfit")]
253 pub take_profit: Option<String>,
254 #[serde(rename = "stopLoss")]
255 pub stop_loss: Option<String>,
256 #[serde(rename = "reduceOnly")]
257 pub reduce_only: Option<bool>,
258 #[serde(rename = "closeOnTrigger")]
259 pub close_on_trigger: Option<bool>,
260}
261
262#[derive(Debug, Clone, Default, Serialize, Deserialize)]
263pub struct CreateOrderRequest {
264 pub category: String,
265 pub symbol: String,
266 pub side: String,
267 #[serde(rename = "orderType")]
268 pub order_type: String,
269 #[serde(skip_serializing_if = "Option::is_none")]
270 pub qty: Option<String>,
271 #[serde(skip_serializing_if = "Option::is_none")]
272 pub price: Option<String>,
273 #[serde(rename = "timeInForce", skip_serializing_if = "Option::is_none")]
274 pub time_in_force: Option<String>,
275 #[serde(rename = "positionIdx", skip_serializing_if = "Option::is_none")]
276 pub position_idx: Option<u64>,
277 #[serde(rename = "orderLinkId", skip_serializing_if = "Option::is_none")]
278 pub order_link_id: Option<String>,
279 #[serde(rename = "triggerPrice", skip_serializing_if = "Option::is_none")]
280 pub trigger_price: Option<String>,
281 #[serde(rename = "takeProfit", skip_serializing_if = "Option::is_none")]
282 pub take_profit: Option<String>,
283 #[serde(rename = "stopLoss", skip_serializing_if = "Option::is_none")]
284 pub stop_loss: Option<String>,
285 #[serde(rename = "reduceOnly", skip_serializing_if = "Option::is_none")]
286 pub reduce_only: Option<bool>,
287 #[serde(rename = "closeOnTrigger", skip_serializing_if = "Option::is_none")]
288 pub close_on_trigger: Option<bool>,
289 #[serde(skip_serializing_if = "Option::is_none")]
290 pub trigger_by: Option<String>,
291 #[serde(skip_serializing_if = "Option::is_none")]
292 pub tp_trigger_by: Option<String>,
293 #[serde(skip_serializing_if = "Option::is_none")]
294 pub sl_trigger_by: Option<String>,
295 #[serde(skip_serializing_if = "Option::is_none")]
296 pub market_unit: Option<String>,
297 #[serde(skip_serializing_if = "Option::is_none")]
298 pub slippage_tolerance_type: Option<String>,
299 #[serde(skip_serializing_if = "Option::is_none")]
300 pub slippage_tolerance: Option<String>,
301 #[serde(skip_serializing_if = "Option::is_none")]
302 pub trigger_direction: Option<i32>,
303 #[serde(skip_serializing_if = "Option::is_none")]
304 pub order_filter: Option<String>,
305}
306
307impl CreateOrderRequest {
308 pub fn builder() -> CreateOrderRequestBuilder {
309 CreateOrderRequestBuilder::default()
310 }
311}
312
313#[derive(Debug, Default)]
315pub struct CreateOrderRequestBuilder {
316 category: Option<String>,
317 symbol: Option<String>,
318 side: Option<String>,
319 order_type: Option<String>,
320 qty: Option<String>,
321 price: Option<String>,
322 time_in_force: Option<String>,
323 position_idx: Option<u64>,
324 order_link_id: Option<String>,
325 trigger_price: Option<String>,
326 take_profit: Option<String>,
327 stop_loss: Option<String>,
328 reduce_only: Option<bool>,
329 close_on_trigger: Option<bool>,
330 trigger_by: Option<String>,
331 tp_trigger_by: Option<String>,
332 sl_trigger_by: Option<String>,
333 market_unit: Option<String>,
334 slippage_tolerance_type: Option<String>,
335 slippage_tolerance: Option<String>,
336 trigger_direction: Option<i32>,
337 order_filter: Option<String>,
338}
339
340impl CreateOrderRequestBuilder {
341 pub fn category(mut self, category: impl Into<String>) -> Self {
342 self.category = Some(category.into());
343 self
344 }
345
346 pub fn symbol(mut self, symbol: impl Into<String>) -> Self {
347 self.symbol = Some(symbol.into());
348 self
349 }
350
351 pub fn side(mut self, side: impl Into<String>) -> Self {
352 self.side = Some(side.into());
353 self
354 }
355
356 pub fn order_type(mut self, order_type: impl Into<String>) -> Self {
357 self.order_type = Some(order_type.into());
358 self
359 }
360
361 pub fn qty(mut self, qty: impl Into<String>) -> Self {
362 self.qty = Some(qty.into());
363 self
364 }
365
366 pub fn price(mut self, price: impl Into<String>) -> Self {
367 self.price = Some(price.into());
368 self
369 }
370
371 pub fn time_in_force(mut self, time_in_force: impl Into<String>) -> Self {
372 self.time_in_force = Some(time_in_force.into());
373 self
374 }
375
376 pub fn position_idx(mut self, position_idx: u64) -> Self {
377 self.position_idx = Some(position_idx);
378 self
379 }
380
381 pub fn order_link_id(mut self, order_link_id: impl Into<String>) -> Self {
382 self.order_link_id = Some(order_link_id.into());
383 self
384 }
385
386 pub fn trigger_price(mut self, trigger_price: impl Into<String>) -> Self {
387 self.trigger_price = Some(trigger_price.into());
388 self
389 }
390
391 pub fn take_profit(mut self, take_profit: impl Into<String>) -> Self {
392 self.take_profit = Some(take_profit.into());
393 self
394 }
395
396 pub fn stop_loss(mut self, stop_loss: impl Into<String>) -> Self {
397 self.stop_loss = Some(stop_loss.into());
398 self
399 }
400
401 pub fn reduce_only(mut self, reduce_only: bool) -> Self {
402 self.reduce_only = Some(reduce_only);
403 self
404 }
405
406 pub fn close_on_trigger(mut self, close_on_trigger: bool) -> Self {
407 self.close_on_trigger = Some(close_on_trigger);
408 self
409 }
410
411 pub fn trigger_by(mut self, trigger_by: impl Into<String>) -> Self {
412 self.trigger_by = Some(trigger_by.into());
413 self
414 }
415
416 pub fn tp_trigger_by(mut self, tp_trigger_by: impl Into<String>) -> Self {
417 self.tp_trigger_by = Some(tp_trigger_by.into());
418 self
419 }
420
421 pub fn sl_trigger_by(mut self, sl_trigger_by: impl Into<String>) -> Self {
422 self.sl_trigger_by = Some(sl_trigger_by.into());
423 self
424 }
425
426 pub fn market_unit(mut self, market_unit: impl Into<String>) -> Self {
427 self.market_unit = Some(market_unit.into());
428 self
429 }
430
431 pub fn slippage_tolerance_type(mut self, slippage_tolerance_type: impl Into<String>) -> Self {
432 self.slippage_tolerance_type = Some(slippage_tolerance_type.into());
433 self
434 }
435
436 pub fn slippage_tolerance(mut self, slippage_tolerance: impl Into<String>) -> Self {
437 self.slippage_tolerance = Some(slippage_tolerance.into());
438 self
439 }
440
441 pub fn trigger_direction(mut self, trigger_direction: i32) -> Self {
442 self.trigger_direction = Some(trigger_direction);
443 self
444 }
445
446 pub fn order_filter(mut self, order_filter: impl Into<String>) -> Self {
447 self.order_filter = Some(order_filter.into());
448 self
449 }
450
451 pub fn build(self) -> CreateOrderRequest {
452 CreateOrderRequest {
453 category: self.category.unwrap_or_else(|| "linear".to_string()),
454 symbol: self.symbol.expect("symbol is required"),
455 side: self.side.expect("side is required"),
456 order_type: self.order_type.expect("order_type is required"),
457 qty: self.qty,
458 price: self.price,
459 time_in_force: self.time_in_force,
460 position_idx: self.position_idx,
461 order_link_id: self.order_link_id,
462 trigger_price: self.trigger_price,
463 take_profit: self.take_profit,
464 stop_loss: self.stop_loss,
465 reduce_only: self.reduce_only,
466 close_on_trigger: self.close_on_trigger,
467 trigger_by: self.trigger_by,
468 tp_trigger_by: self.tp_trigger_by,
469 sl_trigger_by: self.sl_trigger_by,
470 market_unit: self.market_unit,
471 slippage_tolerance_type: self.slippage_tolerance_type,
472 slippage_tolerance: self.slippage_tolerance,
473 trigger_direction: self.trigger_direction,
474 order_filter: self.order_filter,
475 }
476 }
477}
478
479#[derive(Debug, Clone, Serialize, Deserialize)]
480pub struct CreateOrderResponse {
481 pub order_id: String,
482 pub order_link_id: String,
483}
484
485#[cfg(test)]
486mod tests {
487 use super::*;
488
489 #[test]
490 fn test_category_serialization() {
491 let linear_json = serde_json::to_string(&Category::Linear).unwrap();
492 assert_eq!(linear_json, r#""linear""#);
493
494 let inverse_json = serde_json::to_string(&Category::Inverse).unwrap();
495 assert_eq!(inverse_json, r#""inverse""#);
496
497 let spot_json = serde_json::to_string(&Category::Spot).unwrap();
498 assert_eq!(spot_json, r#""spot""#);
499 }
500
501 #[test]
502 fn test_category_deserialization() {
503 let linear: Category = serde_json::from_str(r#""linear""#).unwrap();
504 assert_eq!(linear, Category::Linear);
505
506 let inverse: Category = serde_json::from_str(r#""inverse""#).unwrap();
507 assert_eq!(inverse, Category::Inverse);
508
509 let spot: Category = serde_json::from_str(r#""spot""#).unwrap();
510 assert_eq!(spot, Category::Spot);
511 }
512
513 #[test]
514 fn test_side_serialization() {
515 let buy_json = serde_json::to_string(&Side::Buy).unwrap();
516 assert_eq!(buy_json, r#""Buy""#);
517
518 let sell_json = serde_json::to_string(&Side::Sell).unwrap();
519 assert_eq!(sell_json, r#""Sell""#);
520 }
521
522 #[test]
523 fn test_side_deserialization() {
524 let buy: Side = serde_json::from_str(r#""Buy""#).unwrap();
525 assert_eq!(buy, Side::Buy);
526
527 let sell: Side = serde_json::from_str(r#""Sell""#).unwrap();
528 assert_eq!(sell, Side::Sell);
529 }
530
531 #[test]
532 fn test_order_type_serialization() {
533 let market_json = serde_json::to_string(&OrderType::Market).unwrap();
534 assert_eq!(market_json, r#""Market""#);
535
536 let limit_json = serde_json::to_string(&OrderType::Limit).unwrap();
537 assert_eq!(limit_json, r#""Limit""#);
538 }
539
540 #[test]
541 fn test_order_type_deserialization() {
542 let market: OrderType = serde_json::from_str(r#""Market""#).unwrap();
543 assert_eq!(market, OrderType::Market);
544
545 let limit: OrderType = serde_json::from_str(r#""Limit""#).unwrap();
546 assert_eq!(limit, OrderType::Limit);
547 }
548
549 #[test]
550 fn test_time_in_force_serialization() {
551 let gtc_json = serde_json::to_string(&TimeInForce::GTC).unwrap();
552 assert_eq!(gtc_json, r#""GTC""#);
553
554 let ioc_json = serde_json::to_string(&TimeInForce::IOC).unwrap();
555 assert_eq!(ioc_json, r#""IOC""#);
556
557 let fok_json = serde_json::to_string(&TimeInForce::FOK).unwrap();
558 assert_eq!(fok_json, r#""FOK""#);
559 }
560
561 #[test]
562 fn test_order_status_serialization() {
563 let new_json = serde_json::to_string(&OrderStatus::New).unwrap();
564 assert_eq!(new_json, r#""New""#);
565
566 let filled_json = serde_json::to_string(&OrderStatus::Filled).unwrap();
567 assert_eq!(filled_json, r#""Filled""#);
568
569 let cancelled_json = serde_json::to_string(&OrderStatus::Cancelled).unwrap();
570 assert_eq!(cancelled_json, r#""Cancelled""#);
571 }
572
573 #[test]
574 fn test_server_time_serialization() {
575 let time = ServerTime {
576 time_second: "1234567890".to_string(),
577 time_nano: "1234567890123456789".to_string(),
578 };
579
580 let json = serde_json::to_string(&time).unwrap();
581 assert!(json.contains("\"timeSecond\":\"1234567890\""));
582 assert!(json.contains("\"timeNano\":\"1234567890123456789\""));
583 }
584
585 #[test]
586 fn test_server_time_deserialization() {
587 let json = r#"{"timeSecond":"1234567890","timeNano":"1234567890123456789"}"#;
588 let time: ServerTime = serde_json::from_str(json).unwrap();
589 assert_eq!(time.time_second, "1234567890");
590 assert_eq!(time.time_nano, "1234567890123456789");
591 }
592
593 #[test]
594 fn test_ticker_list_serialization() {
595 let ticker_list = TickerList {
596 list: vec![],
597 next_page_cursor: None,
598 };
599
600 let json = serde_json::to_string(&ticker_list).unwrap();
601 assert!(json.contains("\"list\":[]"));
602 }
603
604 #[test]
605 fn test_create_order_request_default() {
606 let request = CreateOrderRequest {
607 category: "linear".to_string(),
608 symbol: "BTCUSDT".to_string(),
609 side: "Buy".to_string(),
610 order_type: "Limit".to_string(),
611 ..Default::default()
612 };
613
614 assert_eq!(request.category, "linear");
615 assert_eq!(request.symbol, "BTCUSDT");
616 assert_eq!(request.side, "Buy");
617 assert_eq!(request.order_type, "Limit");
618 assert!(request.qty.is_none());
619 assert!(request.price.is_none());
620 }
621
622 #[test]
623 fn test_create_order_request_with_all_fields() {
624 let request = CreateOrderRequest {
625 category: "linear".to_string(),
626 symbol: "BTCUSDT".to_string(),
627 side: "Buy".to_string(),
628 order_type: "Limit".to_string(),
629 qty: Some("0.001".to_string()),
630 price: Some("28000".to_string()),
631 time_in_force: Some("GTC".to_string()),
632 reduce_only: Some(false),
633 take_profit: Some("30000".to_string()),
634 stop_loss: Some("27000".to_string()),
635 ..Default::default()
636 };
637
638 let json = serde_json::to_string(&request).unwrap();
639 assert!(json.contains("\"category\":\"linear\""));
640 assert!(json.contains("\"symbol\":\"BTCUSDT\""));
641 assert!(json.contains("\"qty\":\"0.001\""));
642 assert!(json.contains("\"price\":\"28000\""));
643 assert!(json.contains("\"reduceOnly\":false"));
644 }
645
646 #[test]
647 fn test_create_order_request_builder_basic() {
648 let request = CreateOrderRequest::builder()
649 .category("linear")
650 .symbol("BTCUSDT")
651 .side("Buy")
652 .order_type("Limit")
653 .build();
654
655 assert_eq!(request.category, "linear");
656 assert_eq!(request.symbol, "BTCUSDT");
657 assert_eq!(request.side, "Buy");
658 assert_eq!(request.order_type, "Limit");
659 }
660
661 #[test]
662 #[should_panic(expected = "symbol is required")]
663 fn test_create_order_request_builder_missing_symbol() {
664 let _ = CreateOrderRequest::builder()
665 .category("linear")
666 .side("Buy")
667 .order_type("Limit")
668 .build();
669 }
670
671 #[test]
672 #[should_panic(expected = "side is required")]
673 fn test_create_order_request_builder_missing_side() {
674 let _ = CreateOrderRequest::builder()
675 .category("linear")
676 .symbol("BTCUSDT")
677 .order_type("Limit")
678 .build();
679 }
680
681 #[test]
682 #[should_panic(expected = "order_type is required")]
683 fn test_create_order_request_builder_missing_order_type() {
684 let _ = CreateOrderRequest::builder()
685 .category("linear")
686 .symbol("BTCUSDT")
687 .side("Buy")
688 .build();
689 }
690
691 #[test]
692 fn test_create_order_request_builder_with_optional_fields() {
693 let request = CreateOrderRequest::builder()
694 .category("linear")
695 .symbol("BTCUSDT")
696 .side("Buy")
697 .order_type("Limit")
698 .qty("0.001")
699 .price("28000")
700 .time_in_force("GTC")
701 .position_idx(1)
702 .order_link_id("my_order")
703 .take_profit("30000")
704 .stop_loss("27000")
705 .reduce_only(false)
706 .close_on_trigger(false)
707 .build();
708
709 assert_eq!(request.qty, Some("0.001".to_string()));
710 assert_eq!(request.price, Some("28000".to_string()));
711 assert_eq!(request.time_in_force, Some("GTC".to_string()));
712 assert_eq!(request.position_idx, Some(1));
713 assert_eq!(request.order_link_id, Some("my_order".to_string()));
714 assert_eq!(request.take_profit, Some("30000".to_string()));
715 assert_eq!(request.stop_loss, Some("27000".to_string()));
716 assert_eq!(request.reduce_only, Some(false));
717 assert_eq!(request.close_on_trigger, Some(false));
718 }
719
720 #[test]
721 fn test_create_order_request_builder_default_category() {
722 let request = CreateOrderRequest::builder()
723 .symbol("BTCUSDT")
724 .side("Buy")
725 .order_type("Limit")
726 .build();
727
728 assert_eq!(request.category, "linear");
729 }
730
731 #[test]
732 fn test_create_order_request_builder_chaining() {
733 let request = CreateOrderRequest::builder()
734 .symbol("BTCUSDT")
735 .side("Buy")
736 .order_type("Limit")
737 .qty("0.001")
738 .price("28000")
739 .time_in_force("GTC")
740 .build();
741
742 assert_eq!(request.symbol, "BTCUSDT");
743 assert_eq!(request.side, "Buy");
744 assert_eq!(request.order_type, "Limit");
745 assert_eq!(request.qty, Some("0.001".to_string()));
746 assert_eq!(request.price, Some("28000".to_string()));
747 assert_eq!(request.time_in_force, Some("GTC".to_string()));
748 }
749
750 #[test]
751 fn test_create_order_request_optional_fields_skipped_in_json() {
752 let request = CreateOrderRequest::builder()
753 .symbol("BTCUSDT")
754 .side("Buy")
755 .order_type("Market")
756 .build();
757
758 let json = serde_json::to_string(&request).unwrap();
759 assert!(json.contains("\"symbol\":\"BTCUSDT\""));
760 assert!(json.contains("\"side\":\"Buy\""));
761 assert!(json.contains("\"orderType\":\"Market\""));
762 assert!(!json.contains("\"price\""));
763 assert!(!json.contains("\"qty\""));
764 }
765}