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