1use serde::{Deserialize, Serialize};
14
15#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
19pub enum TimeInForce {
20 #[serde(rename = "good_til_cancelled")]
22 GoodTilCancelled,
23 #[serde(rename = "good_til_day")]
25 GoodTilDay,
26 #[serde(rename = "fill_or_kill")]
28 FillOrKill,
29 #[serde(rename = "immediate_or_cancel")]
31 ImmediateOrCancel,
32}
33
34impl TimeInForce {
35 #[must_use]
37 pub fn as_str(&self) -> &'static str {
38 match self {
39 TimeInForce::GoodTilCancelled => "good_til_cancelled",
40 TimeInForce::GoodTilDay => "good_til_day",
41 TimeInForce::FillOrKill => "fill_or_kill",
42 TimeInForce::ImmediateOrCancel => "immediate_or_cancel",
43 }
44 }
45}
46
47#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
51pub enum OrderSide {
52 Buy,
54 Sell,
56}
57
58#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
62pub enum OrderType {
63 #[serde(rename = "limit")]
65 Limit,
66 #[serde(rename = "market")]
68 Market,
69 #[serde(rename = "stop_limit")]
71 StopLimit,
72 #[serde(rename = "stop_market")]
74 StopMarket,
75 #[serde(rename = "take_limit")]
77 TakeLimit,
78 #[serde(rename = "take_market")]
80 TakeMarket,
81 #[serde(rename = "market_limit")]
83 MarketLimit,
84 #[serde(rename = "trailing_stop")]
86 TrailingStop,
87}
88
89impl OrderType {
90 #[must_use]
92 pub fn as_str(&self) -> &'static str {
93 match self {
94 OrderType::Limit => "limit",
95 OrderType::Market => "market",
96 OrderType::StopLimit => "stop_limit",
97 OrderType::StopMarket => "stop_market",
98 OrderType::TakeLimit => "take_limit",
99 OrderType::TakeMarket => "take_market",
100 OrderType::MarketLimit => "market_limit",
101 OrderType::TrailingStop => "trailing_stop",
102 }
103 }
104}
105
106#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
108pub enum TriggerType {
109 #[serde(rename = "index_price")]
111 IndexPrice,
112 #[serde(rename = "mark_price")]
114 MarkPrice,
115 #[serde(rename = "last_price")]
117 LastPrice,
118}
119
120#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
122pub enum AdvancedOrderType {
123 #[serde(rename = "usd")]
125 Usd,
126 #[serde(rename = "implv")]
128 Implv,
129}
130
131#[derive(Clone, Serialize, Deserialize)]
136pub struct NewOrderRequest {
137 pub instrument_name: String,
139 pub amount: f64,
141 #[serde(rename = "type")]
143 pub order_type: OrderType,
144 pub side: OrderSide,
146 pub price: Option<f64>,
148 pub time_in_force: TimeInForce,
150 pub post_only: Option<bool>,
152 pub reduce_only: Option<bool>,
154 pub label: Option<String>,
156 pub stop_price: Option<f64>,
158 pub trigger: Option<TriggerType>,
160 pub advanced: Option<AdvancedOrderType>,
162 pub max_show: Option<f64>,
164 pub reject_post_only: Option<bool>,
166 pub valid_until: Option<i64>,
168 pub client_order_id: Option<String>,
170}
171
172impl_json_display!(NewOrderRequest);
173impl_json_debug_pretty!(NewOrderRequest);
174
175impl NewOrderRequest {
176 #[must_use]
178 pub fn market_buy(instrument_name: String, amount: f64) -> Self {
179 Self {
180 instrument_name,
181 amount,
182 order_type: OrderType::Market,
183 side: OrderSide::Buy,
184 price: None,
185 time_in_force: TimeInForce::ImmediateOrCancel,
186 post_only: None,
187 reduce_only: None,
188 label: None,
189 stop_price: None,
190 trigger: None,
191 advanced: None,
192 max_show: None,
193 reject_post_only: None,
194 valid_until: None,
195 client_order_id: None,
196 }
197 }
198
199 #[must_use]
201 pub fn market_sell(instrument_name: String, amount: f64) -> Self {
202 Self {
203 instrument_name,
204 amount,
205 order_type: OrderType::Market,
206 side: OrderSide::Sell,
207 price: None,
208 time_in_force: TimeInForce::ImmediateOrCancel,
209 post_only: None,
210 reduce_only: None,
211 label: None,
212 stop_price: None,
213 trigger: None,
214 advanced: None,
215 max_show: None,
216 reject_post_only: None,
217 valid_until: None,
218 client_order_id: None,
219 }
220 }
221
222 #[must_use]
224 pub fn limit_buy(instrument_name: String, amount: f64, price: f64) -> Self {
225 Self {
226 instrument_name,
227 amount,
228 order_type: OrderType::Limit,
229 side: OrderSide::Buy,
230 price: Some(price),
231 time_in_force: TimeInForce::GoodTilCancelled,
232 post_only: None,
233 reduce_only: None,
234 label: None,
235 stop_price: None,
236 trigger: None,
237 advanced: None,
238 max_show: None,
239 reject_post_only: None,
240 valid_until: None,
241 client_order_id: None,
242 }
243 }
244
245 #[must_use]
247 pub fn limit_sell(instrument_name: String, amount: f64, price: f64) -> Self {
248 Self {
249 instrument_name,
250 amount,
251 order_type: OrderType::Limit,
252 side: OrderSide::Sell,
253 price: Some(price),
254 time_in_force: TimeInForce::GoodTilCancelled,
255 post_only: None,
256 reduce_only: None,
257 label: None,
258 stop_price: None,
259 trigger: None,
260 advanced: None,
261 max_show: None,
262 reject_post_only: None,
263 valid_until: None,
264 client_order_id: None,
265 }
266 }
267
268 #[must_use]
270 pub fn with_post_only(mut self, post_only: bool) -> Self {
271 self.post_only = Some(post_only);
272 self
273 }
274
275 #[must_use]
277 pub fn with_reduce_only(mut self, reduce_only: bool) -> Self {
278 self.reduce_only = Some(reduce_only);
279 self
280 }
281
282 #[must_use]
284 pub fn with_label(mut self, label: String) -> Self {
285 self.label = Some(label);
286 self
287 }
288
289 #[must_use]
291 pub fn with_time_in_force(mut self, tif: TimeInForce) -> Self {
292 self.time_in_force = tif;
293 self
294 }
295
296 #[must_use]
298 pub fn with_client_order_id(mut self, client_order_id: String) -> Self {
299 self.client_order_id = Some(client_order_id);
300 self
301 }
302}
303
304#[cfg(test)]
305mod tests {
306 use super::*;
307
308 #[test]
309 fn test_time_in_force_as_str() {
310 assert_eq!(TimeInForce::GoodTilCancelled.as_str(), "good_til_cancelled");
311 assert_eq!(TimeInForce::GoodTilDay.as_str(), "good_til_day");
312 assert_eq!(TimeInForce::FillOrKill.as_str(), "fill_or_kill");
313 assert_eq!(
314 TimeInForce::ImmediateOrCancel.as_str(),
315 "immediate_or_cancel"
316 );
317 }
318
319 #[test]
320 fn test_order_type_as_str() {
321 assert_eq!(OrderType::Limit.as_str(), "limit");
322 assert_eq!(OrderType::Market.as_str(), "market");
323 assert_eq!(OrderType::StopLimit.as_str(), "stop_limit");
324 assert_eq!(OrderType::StopMarket.as_str(), "stop_market");
325 }
326
327 #[test]
328 fn test_new_order_request_market_buy() {
329 let order = NewOrderRequest::market_buy("BTC-PERPETUAL".to_string(), 1.0);
330 assert_eq!(order.instrument_name, "BTC-PERPETUAL");
331 assert_eq!(order.amount, 1.0);
332 assert_eq!(order.order_type, OrderType::Market);
333 assert_eq!(order.side, OrderSide::Buy);
334 assert_eq!(order.price, None);
335 assert_eq!(order.time_in_force, TimeInForce::ImmediateOrCancel);
336 }
337
338 #[test]
339 fn test_new_order_request_limit_sell() {
340 let order = NewOrderRequest::limit_sell("ETH-PERPETUAL".to_string(), 2.0, 3500.0);
341 assert_eq!(order.instrument_name, "ETH-PERPETUAL");
342 assert_eq!(order.amount, 2.0);
343 assert_eq!(order.order_type, OrderType::Limit);
344 assert_eq!(order.side, OrderSide::Sell);
345 assert_eq!(order.price, Some(3500.0));
346 assert_eq!(order.time_in_force, TimeInForce::GoodTilCancelled);
347 }
348
349 #[test]
350 fn test_new_order_request_builder_pattern() {
351 let order = NewOrderRequest::limit_buy("BTC-PERPETUAL".to_string(), 1.0, 50000.0)
352 .with_post_only(true)
353 .with_reduce_only(false)
354 .with_label("test_order".to_string())
355 .with_client_order_id("CLIENT_123".to_string());
356
357 assert_eq!(order.post_only, Some(true));
358 assert_eq!(order.reduce_only, Some(false));
359 assert_eq!(order.label, Some("test_order".to_string()));
360 assert_eq!(order.client_order_id, Some("CLIENT_123".to_string()));
361 }
362
363 #[test]
364 fn test_serialization_roundtrip() {
365 let order = NewOrderRequest::limit_buy("BTC-PERPETUAL".to_string(), 1.0, 50000.0)
366 .with_post_only(true)
367 .with_label("test_order".to_string());
368
369 let json = serde_json::to_string(&order).unwrap();
370 let deserialized: NewOrderRequest = serde_json::from_str(&json).unwrap();
371
372 assert_eq!(order.instrument_name, deserialized.instrument_name);
373 assert_eq!(order.amount, deserialized.amount);
374 assert_eq!(order.order_type, deserialized.order_type);
375 assert_eq!(order.side, deserialized.side);
376 assert_eq!(order.price, deserialized.price);
377 assert_eq!(order.post_only, deserialized.post_only);
378 assert_eq!(order.label, deserialized.label);
379 }
380}