1use pretty_simple_display::{DebugPretty, DisplaySimple};
7use serde::{Deserialize, Serialize};
8
9#[derive(DebugPretty, DisplaySimple, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
11pub enum TimeInForce {
12 #[serde(rename = "good_til_cancelled")]
14 GoodTilCancelled,
15 #[serde(rename = "good_til_day")]
17 GoodTilDay,
18 #[serde(rename = "fill_or_kill")]
20 FillOrKill,
21 #[serde(rename = "immediate_or_cancel")]
23 ImmediateOrCancel,
24}
25
26impl TimeInForce {
27 pub fn as_str(&self) -> &'static str {
29 match self {
30 TimeInForce::GoodTilCancelled => "good_til_cancelled",
31 TimeInForce::GoodTilDay => "good_til_day",
32 TimeInForce::FillOrKill => "fill_or_kill",
33 TimeInForce::ImmediateOrCancel => "immediate_or_cancel",
34 }
35 }
36}
37
38#[derive(DebugPretty, DisplaySimple, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
40pub enum OrderSide {
41 #[serde(rename = "buy")]
43 Buy,
44 #[serde(rename = "sell")]
46 Sell,
47}
48
49impl OrderSide {
50 pub fn as_str(&self) -> &'static str {
52 match self {
53 OrderSide::Buy => "buy",
54 OrderSide::Sell => "sell",
55 }
56 }
57}
58
59#[derive(DebugPretty, DisplaySimple, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
61pub enum OrderType {
62 #[serde(rename = "limit")]
64 Limit,
65 #[serde(rename = "market")]
67 Market,
68 #[serde(rename = "stop_limit")]
70 StopLimit,
71 #[serde(rename = "stop_market")]
73 StopMarket,
74 #[serde(rename = "take_limit")]
76 TakeLimit,
77 #[serde(rename = "take_market")]
79 TakeMarket,
80 #[serde(rename = "market_limit")]
82 MarketLimit,
83 #[serde(rename = "trailing_stop")]
85 TrailingStop,
86}
87
88impl OrderType {
89 pub fn as_str(&self) -> &'static str {
91 match self {
92 OrderType::Limit => "limit",
93 OrderType::Market => "market",
94 OrderType::StopLimit => "stop_limit",
95 OrderType::StopMarket => "stop_market",
96 OrderType::TakeLimit => "take_limit",
97 OrderType::TakeMarket => "take_market",
98 OrderType::MarketLimit => "market_limit",
99 OrderType::TrailingStop => "trailing_stop",
100 }
101 }
102}
103
104#[derive(DebugPretty, DisplaySimple, Clone, Serialize, Deserialize)]
106pub struct NewOrderRequest {
107 pub symbol: String,
109 pub side: OrderSide,
111 pub order_type: OrderType,
113 pub quantity: f64,
115 pub price: Option<f64>,
117 pub time_in_force: TimeInForce,
119 pub client_order_id: Option<String>,
121}
122
123#[derive(DebugPretty, DisplaySimple, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
125pub enum OrderStatus {
126 New,
128 PartiallyFilled,
130 Filled,
132 DoneForDay,
134 Canceled,
136 Replaced,
138 PendingCancel,
140 Stopped,
142 Rejected,
144 Suspended,
146 PendingNew,
148 Calculated,
150 Expired,
152 AcceptedForBidding,
154 PendingReplace,
156}
157
158#[derive(DebugPretty, DisplaySimple, Clone, Serialize, Deserialize)]
160pub struct OrderInfo {
161 pub amount: f64,
163 pub api: bool,
165 pub average_price: f64,
167 pub creation_timestamp: u64,
169 pub direction: String,
171 pub filled_amount: f64,
173 pub instrument_name: String,
175 pub is_liquidation: bool,
177 pub label: String,
179 pub last_update_timestamp: u64,
181 pub max_show: Option<f64>,
183 pub order_id: String,
185 pub order_state: String,
187 pub order_type: String,
189 pub original_order_type: Option<String>,
191 pub post_only: bool,
193 pub price: f64,
195 pub profit_loss: Option<f64>,
197 pub reduce_only: bool,
199 pub replaced: bool,
201 pub risk_reducing: bool,
203 pub time_in_force: String,
205 pub triggered: Option<bool>,
207 pub trigger: Option<String>,
209 pub usd: Option<f64>,
211 pub web: bool,
213}
214
215#[cfg(test)]
216mod tests {
217 use super::*;
218
219 #[test]
220 fn test_time_in_force_as_str() {
221 assert_eq!(TimeInForce::GoodTilCancelled.as_str(), "good_til_cancelled");
222 assert_eq!(TimeInForce::GoodTilDay.as_str(), "good_til_day");
223 assert_eq!(TimeInForce::FillOrKill.as_str(), "fill_or_kill");
224 assert_eq!(
225 TimeInForce::ImmediateOrCancel.as_str(),
226 "immediate_or_cancel"
227 );
228 }
229
230 #[test]
231 fn test_time_in_force_serialization() {
232 let tif = TimeInForce::GoodTilCancelled;
233 let json = serde_json::to_string(&tif).unwrap();
234 assert_eq!(json, "\"good_til_cancelled\"");
235
236 let deserialized: TimeInForce = serde_json::from_str(&json).unwrap();
237 assert_eq!(deserialized, TimeInForce::GoodTilCancelled);
238 }
239
240 #[test]
241 fn test_order_side_serialization() {
242 let buy_side = OrderSide::Buy;
243 let sell_side = OrderSide::Sell;
244
245 let buy_json = serde_json::to_string(&buy_side).unwrap();
246 let sell_json = serde_json::to_string(&sell_side).unwrap();
247
248 assert_eq!(buy_json, "\"buy\"");
249 assert_eq!(sell_json, "\"sell\"");
250
251 let buy_deserialized: OrderSide = serde_json::from_str(&buy_json).unwrap();
252 let sell_deserialized: OrderSide = serde_json::from_str(&sell_json).unwrap();
253
254 assert_eq!(buy_deserialized, OrderSide::Buy);
255 assert_eq!(sell_deserialized, OrderSide::Sell);
256 }
257
258 #[test]
259 fn test_order_type_as_str() {
260 assert_eq!(OrderType::Limit.as_str(), "limit");
261 assert_eq!(OrderType::Market.as_str(), "market");
262 assert_eq!(OrderType::StopLimit.as_str(), "stop_limit");
263 assert_eq!(OrderType::StopMarket.as_str(), "stop_market");
264 assert_eq!(OrderType::TakeLimit.as_str(), "take_limit");
265 assert_eq!(OrderType::TakeMarket.as_str(), "take_market");
266 assert_eq!(OrderType::MarketLimit.as_str(), "market_limit");
267 assert_eq!(OrderType::TrailingStop.as_str(), "trailing_stop");
268 }
269
270 #[test]
271 fn test_order_type_serialization() {
272 let order_type = OrderType::Limit;
273 let json = serde_json::to_string(&order_type).unwrap();
274 assert_eq!(json, "\"limit\"");
275
276 let deserialized: OrderType = serde_json::from_str(&json).unwrap();
277 assert_eq!(deserialized, OrderType::Limit);
278 }
279
280 #[test]
281 fn test_new_order_request_creation() {
282 let order = NewOrderRequest {
283 symbol: "BTC-PERPETUAL".to_string(),
284 side: OrderSide::Buy,
285 order_type: OrderType::Limit,
286 quantity: 1.0,
287 price: Some(50000.0),
288 time_in_force: TimeInForce::GoodTilCancelled,
289 client_order_id: Some("CLIENT_ORDER_123".to_string()),
290 };
291
292 assert_eq!(order.symbol, "BTC-PERPETUAL");
293 assert_eq!(order.side, OrderSide::Buy);
294 assert_eq!(order.order_type, OrderType::Limit);
295 assert_eq!(order.quantity, 1.0);
296 assert_eq!(order.price, Some(50000.0));
297 assert_eq!(order.time_in_force, TimeInForce::GoodTilCancelled);
298 assert_eq!(order.client_order_id, Some("CLIENT_ORDER_123".to_string()));
299 }
300
301 #[test]
302 fn test_new_order_request_market_order() {
303 let market_order = NewOrderRequest {
304 symbol: "ETH-PERPETUAL".to_string(),
305 side: OrderSide::Sell,
306 order_type: OrderType::Market,
307 quantity: 2.0,
308 price: None, time_in_force: TimeInForce::ImmediateOrCancel,
310 client_order_id: None,
311 };
312
313 assert_eq!(market_order.order_type, OrderType::Market);
314 assert_eq!(market_order.price, None);
315 assert_eq!(market_order.client_order_id, None);
316 }
317
318 #[test]
319 fn test_order_status_variants() {
320 let statuses = vec![
321 OrderStatus::New,
322 OrderStatus::PartiallyFilled,
323 OrderStatus::Filled,
324 OrderStatus::DoneForDay,
325 OrderStatus::Canceled,
326 OrderStatus::Replaced,
327 OrderStatus::PendingCancel,
328 OrderStatus::Stopped,
329 OrderStatus::Rejected,
330 OrderStatus::Suspended,
331 OrderStatus::PendingNew,
332 OrderStatus::Calculated,
333 OrderStatus::Expired,
334 OrderStatus::AcceptedForBidding,
335 OrderStatus::PendingReplace,
336 ];
337
338 for status in statuses {
340 let cloned = status;
341 assert_eq!(status, cloned);
342 }
343 }
344
345 #[test]
346 fn test_order_info_creation() {
347 let order_info = OrderInfo {
348 amount: 1.0,
349 api: true,
350 average_price: 50000.0,
351 creation_timestamp: 1640995200000,
352 direction: "buy".to_string(),
353 filled_amount: 0.5,
354 instrument_name: "BTC-PERPETUAL".to_string(),
355 is_liquidation: false,
356 label: "test_order".to_string(),
357 last_update_timestamp: 1640995300000,
358 max_show: Some(0.8),
359 order_id: "ORDER_123".to_string(),
360 order_state: "open".to_string(),
361 order_type: "limit".to_string(),
362 original_order_type: None,
363 post_only: false,
364 price: 50000.0,
365 profit_loss: Some(100.0),
366 reduce_only: false,
367 replaced: false,
368 risk_reducing: false,
369 time_in_force: "good_til_cancelled".to_string(),
370 triggered: Some(false),
371 trigger: None,
372 usd: Some(50000.0),
373 web: false,
374 };
375
376 assert_eq!(order_info.amount, 1.0);
377 assert_eq!(order_info.instrument_name, "BTC-PERPETUAL");
378 assert_eq!(order_info.filled_amount, 0.5);
379 assert_eq!(order_info.max_show, Some(0.8));
380 assert_eq!(order_info.profit_loss, Some(100.0));
381 assert!(order_info.api);
382 assert!(!order_info.is_liquidation);
383 }
384
385 #[test]
386 fn test_order_info_optional_fields() {
387 let minimal_order_info = OrderInfo {
388 amount: 1.0,
389 api: true,
390 average_price: 0.0,
391 creation_timestamp: 1640995200000,
392 direction: "buy".to_string(),
393 filled_amount: 0.0,
394 instrument_name: "BTC-PERPETUAL".to_string(),
395 is_liquidation: false,
396 label: "".to_string(),
397 last_update_timestamp: 1640995200000,
398 max_show: None,
399 order_id: "ORDER_123".to_string(),
400 order_state: "new".to_string(),
401 order_type: "limit".to_string(),
402 original_order_type: None,
403 post_only: false,
404 price: 50000.0,
405 profit_loss: None,
406 reduce_only: false,
407 replaced: false,
408 risk_reducing: false,
409 time_in_force: "good_til_cancelled".to_string(),
410 triggered: None,
411 trigger: None,
412 usd: None,
413 web: false,
414 };
415
416 assert_eq!(minimal_order_info.max_show, None);
417 assert_eq!(minimal_order_info.profit_loss, None);
418 assert_eq!(minimal_order_info.triggered, None);
419 assert_eq!(minimal_order_info.trigger, None);
420 assert_eq!(minimal_order_info.usd, None);
421 }
422
423 #[test]
424 fn test_serialization_roundtrip() {
425 let order = NewOrderRequest {
426 symbol: "BTC-PERPETUAL".to_string(),
427 side: OrderSide::Buy,
428 order_type: OrderType::Limit,
429 quantity: 1.0,
430 price: Some(50000.0),
431 time_in_force: TimeInForce::GoodTilCancelled,
432 client_order_id: Some("CLIENT_ORDER_123".to_string()),
433 };
434
435 let json = serde_json::to_string(&order).unwrap();
436 let deserialized: NewOrderRequest = serde_json::from_str(&json).unwrap();
437
438 assert_eq!(order.symbol, deserialized.symbol);
439 assert_eq!(order.side, deserialized.side);
440 assert_eq!(order.order_type, deserialized.order_type);
441 assert_eq!(order.quantity, deserialized.quantity);
442 assert_eq!(order.price, deserialized.price);
443 assert_eq!(order.time_in_force, deserialized.time_in_force);
444 assert_eq!(order.client_order_id, deserialized.client_order_id);
445 }
446
447 #[test]
448 fn test_debug_and_display_implementations() {
449 let order = NewOrderRequest {
450 symbol: "BTC-PERPETUAL".to_string(),
451 side: OrderSide::Buy,
452 order_type: OrderType::Limit,
453 quantity: 1.0,
454 price: Some(50000.0),
455 time_in_force: TimeInForce::GoodTilCancelled,
456 client_order_id: Some("CLIENT_ORDER_123".to_string()),
457 };
458
459 let debug_str = format!("{:?}", order);
460 let display_str = format!("{}", order);
461
462 assert!(debug_str.contains("BTC-PERPETUAL"));
463 assert!(display_str.contains("BTC-PERPETUAL"));
464 }
465
466 #[test]
467 fn test_enum_equality_and_cloning() {
468 let tif1 = TimeInForce::GoodTilCancelled;
469 let tif2 = tif1;
470 assert_eq!(tif1, tif2);
471
472 let side1 = OrderSide::Buy;
473 let side2 = side1;
474 assert_eq!(side1, side2);
475
476 let type1 = OrderType::Limit;
477 let type2 = type1;
478 assert_eq!(type1, type2);
479
480 let status1 = OrderStatus::New;
481 let status2 = status1;
482 assert_eq!(status1, status2);
483 }
484
485 #[test]
486 fn test_order_type_variants_coverage() {
487 let types = vec![
489 OrderType::Limit,
490 OrderType::Market,
491 OrderType::StopLimit,
492 OrderType::StopMarket,
493 OrderType::TakeLimit,
494 OrderType::TakeMarket,
495 OrderType::MarketLimit,
496 OrderType::TrailingStop,
497 ];
498
499 for order_type in types {
500 let str_repr = order_type.as_str();
502 assert!(!str_repr.is_empty());
503
504 let json = serde_json::to_string(&order_type).unwrap();
506 let deserialized: OrderType = serde_json::from_str(&json).unwrap();
507 assert_eq!(order_type, deserialized);
508 }
509 }
510
511 #[test]
512 fn test_time_in_force_variants_coverage() {
513 let tifs = vec![
514 TimeInForce::GoodTilCancelled,
515 TimeInForce::GoodTilDay,
516 TimeInForce::FillOrKill,
517 TimeInForce::ImmediateOrCancel,
518 ];
519
520 for tif in tifs {
521 let str_repr = tif.as_str();
523 assert!(!str_repr.is_empty());
524
525 let json = serde_json::to_string(&tif).unwrap();
527 let deserialized: TimeInForce = serde_json::from_str(&json).unwrap();
528 assert_eq!(tif, deserialized);
529 }
530 }
531}