1use pretty_simple_display::{DebugPretty, DisplaySimple};
6use serde::{Deserialize, Serialize};
7
8#[derive(Clone, Copy, PartialEq, Eq, Serialize, Deserialize, DebugPretty, DisplaySimple)]
10#[serde(rename_all = "snake_case")]
11pub enum OrderType {
12 Limit,
14 Market,
16 StopLimit,
18 StopMarket,
20 TakeLimit,
22 TakeMarket,
24 MarketLimit,
26 TrailingStop,
28}
29
30impl OrderType {
31 #[must_use]
33 pub fn as_str(&self) -> &'static str {
34 match self {
35 OrderType::Limit => "limit",
36 OrderType::Market => "market",
37 OrderType::StopLimit => "stop_limit",
38 OrderType::StopMarket => "stop_market",
39 OrderType::TakeLimit => "take_limit",
40 OrderType::TakeMarket => "take_market",
41 OrderType::MarketLimit => "market_limit",
42 OrderType::TrailingStop => "trailing_stop",
43 }
44 }
45}
46
47#[derive(Clone, Copy, PartialEq, Eq, Serialize, Deserialize, DebugPretty, DisplaySimple)]
49#[serde(rename_all = "snake_case")]
50pub enum TimeInForce {
51 #[serde(rename = "good_til_cancelled")]
53 GoodTilCancelled,
54 #[serde(rename = "good_til_day")]
56 GoodTilDay,
57 #[serde(rename = "fill_or_kill")]
59 FillOrKill,
60 #[serde(rename = "immediate_or_cancel")]
62 ImmediateOrCancel,
63}
64
65impl TimeInForce {
66 #[must_use]
68 pub fn as_str(&self) -> &'static str {
69 match self {
70 TimeInForce::GoodTilCancelled => "good_til_cancelled",
71 TimeInForce::GoodTilDay => "good_til_day",
72 TimeInForce::FillOrKill => "fill_or_kill",
73 TimeInForce::ImmediateOrCancel => "immediate_or_cancel",
74 }
75 }
76}
77
78#[derive(Clone, Copy, PartialEq, Eq, Serialize, Deserialize, DebugPretty, DisplaySimple)]
80#[serde(rename_all = "snake_case")]
81pub enum Trigger {
82 IndexPrice,
84 MarkPrice,
86 LastPrice,
88}
89
90#[derive(Clone, Serialize, Deserialize, DebugPretty, DisplaySimple)]
92pub struct OrderRequest {
93 pub instrument_name: String,
95 pub amount: f64,
97 #[serde(rename = "type", skip_serializing_if = "Option::is_none")]
99 pub order_type: Option<OrderType>,
100 #[serde(skip_serializing_if = "Option::is_none")]
102 pub label: Option<String>,
103 #[serde(skip_serializing_if = "Option::is_none")]
105 pub price: Option<f64>,
106 #[serde(skip_serializing_if = "Option::is_none")]
108 pub time_in_force: Option<TimeInForce>,
109 #[serde(skip_serializing_if = "Option::is_none")]
111 pub max_show: Option<f64>,
112 #[serde(skip_serializing_if = "Option::is_none")]
114 pub post_only: Option<bool>,
115 #[serde(skip_serializing_if = "Option::is_none")]
117 pub reduce_only: Option<bool>,
118 #[serde(skip_serializing_if = "Option::is_none")]
120 pub trigger_price: Option<f64>,
121 #[serde(skip_serializing_if = "Option::is_none")]
123 pub trigger: Option<Trigger>,
124 #[serde(skip_serializing_if = "Option::is_none")]
126 pub advanced: Option<String>,
127 #[serde(skip_serializing_if = "Option::is_none")]
129 pub mmp: Option<bool>,
130 #[serde(skip_serializing_if = "Option::is_none")]
132 pub valid_until: Option<u64>,
133}
134
135impl OrderRequest {
136 #[must_use]
138 pub fn limit(instrument_name: String, amount: f64, price: f64) -> Self {
139 Self {
140 instrument_name,
141 amount,
142 order_type: Some(OrderType::Limit),
143 label: None,
144 price: Some(price),
145 time_in_force: None,
146 max_show: None,
147 post_only: None,
148 reduce_only: None,
149 trigger_price: None,
150 trigger: None,
151 advanced: None,
152 mmp: None,
153 valid_until: None,
154 }
155 }
156
157 #[must_use]
159 pub fn market(instrument_name: String, amount: f64) -> Self {
160 Self {
161 instrument_name,
162 amount,
163 order_type: Some(OrderType::Market),
164 label: None,
165 price: None,
166 time_in_force: None,
167 max_show: None,
168 post_only: None,
169 reduce_only: None,
170 trigger_price: None,
171 trigger: None,
172 advanced: None,
173 mmp: None,
174 valid_until: None,
175 }
176 }
177
178 #[must_use]
180 pub fn with_label(mut self, label: String) -> Self {
181 self.label = Some(label);
182 self
183 }
184
185 #[must_use]
187 pub fn with_time_in_force(mut self, tif: TimeInForce) -> Self {
188 self.time_in_force = Some(tif);
189 self
190 }
191
192 #[must_use]
194 pub fn with_post_only(mut self, post_only: bool) -> Self {
195 self.post_only = Some(post_only);
196 self
197 }
198
199 #[must_use]
201 pub fn with_reduce_only(mut self, reduce_only: bool) -> Self {
202 self.reduce_only = Some(reduce_only);
203 self
204 }
205
206 #[must_use]
208 pub fn with_max_show(mut self, max_show: f64) -> Self {
209 self.max_show = Some(max_show);
210 self
211 }
212
213 #[must_use]
215 pub fn with_trigger(mut self, trigger_price: f64, trigger: Trigger) -> Self {
216 self.trigger_price = Some(trigger_price);
217 self.trigger = Some(trigger);
218 self
219 }
220
221 #[must_use]
223 pub fn with_mmp(mut self, mmp: bool) -> Self {
224 self.mmp = Some(mmp);
225 self
226 }
227}
228
229#[derive(Clone, Serialize, Deserialize, DebugPretty, DisplaySimple)]
231pub struct EditOrderRequest {
232 pub order_id: String,
234 pub amount: f64,
236 #[serde(skip_serializing_if = "Option::is_none")]
238 pub price: Option<f64>,
239 #[serde(skip_serializing_if = "Option::is_none")]
241 pub post_only: Option<bool>,
242 #[serde(skip_serializing_if = "Option::is_none")]
244 pub reduce_only: Option<bool>,
245 #[serde(skip_serializing_if = "Option::is_none")]
247 pub advanced: Option<String>,
248 #[serde(skip_serializing_if = "Option::is_none")]
250 pub trigger_price: Option<f64>,
251 #[serde(skip_serializing_if = "Option::is_none")]
253 pub mmp: Option<bool>,
254 #[serde(skip_serializing_if = "Option::is_none")]
256 pub valid_until: Option<u64>,
257}
258
259impl EditOrderRequest {
260 #[must_use]
262 pub fn new(order_id: String, amount: f64) -> Self {
263 Self {
264 order_id,
265 amount,
266 price: None,
267 post_only: None,
268 reduce_only: None,
269 advanced: None,
270 trigger_price: None,
271 mmp: None,
272 valid_until: None,
273 }
274 }
275
276 #[must_use]
278 pub fn with_price(mut self, price: f64) -> Self {
279 self.price = Some(price);
280 self
281 }
282
283 #[must_use]
285 pub fn with_post_only(mut self, post_only: bool) -> Self {
286 self.post_only = Some(post_only);
287 self
288 }
289
290 #[must_use]
292 pub fn with_reduce_only(mut self, reduce_only: bool) -> Self {
293 self.reduce_only = Some(reduce_only);
294 self
295 }
296}
297
298#[derive(Clone, Serialize, Deserialize, DebugPretty, DisplaySimple)]
300pub struct TradeExecution {
301 pub trade_id: String,
303 pub instrument_name: String,
305 pub direction: String,
307 pub amount: f64,
309 pub price: f64,
311 pub fee: f64,
313 pub fee_currency: String,
315 pub order_id: String,
317 pub order_type: String,
319 pub timestamp: u64,
321 #[serde(skip_serializing_if = "Option::is_none")]
323 pub liquidity: Option<String>,
324 #[serde(skip_serializing_if = "Option::is_none")]
326 pub index_price: Option<f64>,
327 #[serde(skip_serializing_if = "Option::is_none")]
329 pub mark_price: Option<f64>,
330 #[serde(skip_serializing_if = "Option::is_none")]
332 pub profit_loss: Option<f64>,
333}
334
335#[derive(Clone, Serialize, Deserialize, DebugPretty, DisplaySimple)]
337pub struct OrderInfo {
338 pub order_id: String,
340 pub instrument_name: String,
342 pub direction: String,
344 pub amount: f64,
346 #[serde(default)]
348 pub filled_amount: f64,
349 #[serde(skip_serializing_if = "Option::is_none")]
351 pub price: Option<f64>,
352 #[serde(skip_serializing_if = "Option::is_none")]
354 pub average_price: Option<f64>,
355 pub order_type: String,
357 pub order_state: String,
359 #[serde(skip_serializing_if = "Option::is_none")]
361 pub time_in_force: Option<String>,
362 #[serde(default)]
364 pub label: String,
365 pub creation_timestamp: u64,
367 pub last_update_timestamp: u64,
369 #[serde(default)]
371 pub api: bool,
372 #[serde(default)]
374 pub web: bool,
375 #[serde(default)]
377 pub post_only: bool,
378 #[serde(default)]
380 pub reduce_only: bool,
381 #[serde(default)]
383 pub is_liquidation: bool,
384 #[serde(skip_serializing_if = "Option::is_none")]
386 pub max_show: Option<f64>,
387 #[serde(skip_serializing_if = "Option::is_none")]
389 pub profit_loss: Option<f64>,
390 #[serde(skip_serializing_if = "Option::is_none")]
392 pub usd: Option<f64>,
393 #[serde(skip_serializing_if = "Option::is_none")]
395 pub implv: Option<f64>,
396 #[serde(skip_serializing_if = "Option::is_none")]
398 pub trigger_price: Option<f64>,
399 #[serde(skip_serializing_if = "Option::is_none")]
401 pub trigger: Option<String>,
402 #[serde(skip_serializing_if = "Option::is_none")]
404 pub triggered: Option<bool>,
405 #[serde(default)]
407 pub replaced: bool,
408 #[serde(default)]
410 pub mmp: bool,
411 #[serde(default)]
413 pub mmp_cancelled: bool,
414}
415
416#[derive(Clone, Serialize, Deserialize, DebugPretty, DisplaySimple)]
418pub struct OrderResponse {
419 pub order: OrderInfo,
421 #[serde(default)]
423 pub trades: Vec<TradeExecution>,
424}
425
426#[cfg(test)]
427mod tests {
428 use super::*;
429
430 #[test]
431 fn test_order_type_serialization() {
432 let order_type = OrderType::Limit;
433 let json = serde_json::to_string(&order_type).expect("serialize");
434 assert_eq!(json, "\"limit\"");
435
436 let order_type = OrderType::StopLimit;
437 let json = serde_json::to_string(&order_type).expect("serialize");
438 assert_eq!(json, "\"stop_limit\"");
439 }
440
441 #[test]
442 fn test_time_in_force_serialization() {
443 let tif = TimeInForce::GoodTilCancelled;
444 let json = serde_json::to_string(&tif).expect("serialize");
445 assert_eq!(json, "\"good_til_cancelled\"");
446
447 let tif = TimeInForce::ImmediateOrCancel;
448 let json = serde_json::to_string(&tif).expect("serialize");
449 assert_eq!(json, "\"immediate_or_cancel\"");
450 }
451
452 #[test]
453 fn test_order_request_limit() {
454 let request = OrderRequest::limit("BTC-PERPETUAL".to_string(), 100.0, 50000.0)
455 .with_label("test_order".to_string())
456 .with_post_only(true);
457
458 assert_eq!(request.instrument_name, "BTC-PERPETUAL");
459 assert_eq!(request.amount, 100.0);
460 assert_eq!(request.price, Some(50000.0));
461 assert_eq!(request.order_type, Some(OrderType::Limit));
462 assert_eq!(request.label, Some("test_order".to_string()));
463 assert_eq!(request.post_only, Some(true));
464 }
465
466 #[test]
467 fn test_order_request_market() {
468 let request =
469 OrderRequest::market("ETH-PERPETUAL".to_string(), 10.0).with_reduce_only(true);
470
471 assert_eq!(request.instrument_name, "ETH-PERPETUAL");
472 assert_eq!(request.amount, 10.0);
473 assert_eq!(request.price, None);
474 assert_eq!(request.order_type, Some(OrderType::Market));
475 assert_eq!(request.reduce_only, Some(true));
476 }
477
478 #[test]
479 fn test_edit_order_request() {
480 let request = EditOrderRequest::new("order123".to_string(), 200.0).with_price(51000.0);
481
482 assert_eq!(request.order_id, "order123");
483 assert_eq!(request.amount, 200.0);
484 assert_eq!(request.price, Some(51000.0));
485 }
486
487 #[test]
488 fn test_order_type_as_str() {
489 assert_eq!(OrderType::Limit.as_str(), "limit");
490 assert_eq!(OrderType::Market.as_str(), "market");
491 assert_eq!(OrderType::StopLimit.as_str(), "stop_limit");
492 assert_eq!(OrderType::TrailingStop.as_str(), "trailing_stop");
493 }
494
495 #[test]
496 fn test_time_in_force_as_str() {
497 assert_eq!(TimeInForce::GoodTilCancelled.as_str(), "good_til_cancelled");
498 assert_eq!(TimeInForce::FillOrKill.as_str(), "fill_or_kill");
499 assert_eq!(
500 TimeInForce::ImmediateOrCancel.as_str(),
501 "immediate_or_cancel"
502 );
503 }
504}