1use crate::impl_json_display;
7use serde::{Deserialize, Deserializer, Serialize};
8
9const DEFAULT_ORDER_SELL_SIZE: f64 = 0.0;
10const DEFAULT_ORDER_BUY_SIZE: f64 = 10000.0;
11
12#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Default)]
14#[serde(rename_all = "UPPERCASE")]
15pub enum Direction {
16 #[default]
18 Buy,
19 Sell,
21}
22
23impl_json_display!(Direction);
24
25#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Default)]
27#[serde(rename_all = "UPPERCASE")]
28pub enum OrderType {
29 #[default]
31 Limit,
32 Market,
34 Quote,
36 Stop,
38 StopLimit,
40}
41
42#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Default)]
47#[serde(rename_all = "UPPERCASE")]
48pub enum Status {
49 Amended,
51 Deleted,
53 #[serde(rename = "FULLY_CLOSED")]
55 FullyClosed,
56 Opened,
58 #[serde(rename = "PARTIALLY_CLOSED")]
60 PartiallyClosed,
61 Closed,
63 #[default]
65 Open,
66 Updated,
68 Accepted,
70 Rejected,
72 Working,
74 Filled,
76 Cancelled,
78 Expired,
80}
81
82#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Default)]
84pub enum TimeInForce {
85 #[serde(rename = "GOOD_TILL_CANCELLED")]
87 #[default]
88 GoodTillCancelled,
89 #[serde(rename = "GOOD_TILL_DATE")]
91 GoodTillDate,
92 #[serde(rename = "IMMEDIATE_OR_CANCEL")]
94 ImmediateOrCancel,
95 #[serde(rename = "FILL_OR_KILL")]
97 FillOrKill,
98}
99
100#[derive(Debug, Clone, Serialize)]
102pub struct CreateOrderRequest {
103 pub epic: String,
105 pub direction: Direction,
107 pub size: f64,
109 #[serde(rename = "orderType")]
111 pub order_type: OrderType,
112 #[serde(rename = "timeInForce")]
114 pub time_in_force: TimeInForce,
115 #[serde(rename = "level", skip_serializing_if = "Option::is_none")]
117 pub level: Option<f64>,
118 #[serde(rename = "guaranteedStop", skip_serializing_if = "Option::is_none")]
120 pub guaranteed_stop: Option<bool>,
121 #[serde(rename = "stopLevel", skip_serializing_if = "Option::is_none")]
123 pub stop_level: Option<f64>,
124 #[serde(rename = "stopDistance", skip_serializing_if = "Option::is_none")]
126 pub stop_distance: Option<f64>,
127 #[serde(rename = "limitLevel", skip_serializing_if = "Option::is_none")]
129 pub limit_level: Option<f64>,
130 #[serde(rename = "limitDistance", skip_serializing_if = "Option::is_none")]
132 pub limit_distance: Option<f64>,
133 #[serde(rename = "expiry", skip_serializing_if = "Option::is_none")]
135 pub expiry: Option<String>,
136 #[serde(rename = "dealReference", skip_serializing_if = "Option::is_none")]
138 pub deal_reference: Option<String>,
139 #[serde(rename = "forceOpen", skip_serializing_if = "Option::is_none")]
141 pub force_open: Option<bool>,
142 #[serde(rename = "currencyCode", skip_serializing_if = "Option::is_none")]
144 pub currency_code: Option<String>,
145}
146
147impl CreateOrderRequest {
148 pub fn market(epic: String, direction: Direction, size: f64) -> Self {
150 Self {
151 epic,
152 direction,
153 size,
154 order_type: OrderType::Market,
155 time_in_force: TimeInForce::FillOrKill,
156 level: None,
157 guaranteed_stop: None,
158 stop_level: None,
159 stop_distance: None,
160 limit_level: None,
161 limit_distance: None,
162 expiry: None,
163 deal_reference: None,
164 force_open: Some(true),
165 currency_code: None,
166 }
167 }
168
169 pub fn limit(epic: String, direction: Direction, size: f64, level: f64) -> Self {
171 Self {
172 epic,
173 direction,
174 size,
175 order_type: OrderType::Limit,
176 time_in_force: TimeInForce::GoodTillCancelled,
177 level: Some(level),
178 guaranteed_stop: None,
179 stop_level: None,
180 stop_distance: None,
181 limit_level: None,
182 limit_distance: None,
183 expiry: None,
184 deal_reference: None,
185 force_open: Some(true),
186 currency_code: None,
187 }
188 }
189
190 #[allow(clippy::ptr_arg)]
223 pub fn sell_option_to_market(
224 epic: &String,
225 size: &f64,
226 expiry: &Option<String>,
227 deal_reference: &Option<String>,
228 currency_code: &Option<String>,
229 ) -> Self {
230 let rounded_size = (size * 100.0).floor() / 100.0;
231 let currency_code = if let Some(code) = currency_code {
232 Some(code.clone())
233 } else {
234 Some("EUR".to_string())
235 };
236
237 Self {
238 epic: epic.clone(),
239 direction: Direction::Sell,
240 size: rounded_size,
241 order_type: OrderType::Limit,
242 time_in_force: TimeInForce::FillOrKill,
243 level: Some(DEFAULT_ORDER_SELL_SIZE),
244 guaranteed_stop: Some(false),
245 stop_level: None,
246 stop_distance: None,
247 limit_level: None,
248 limit_distance: None,
249 expiry: expiry.clone(),
250 deal_reference: deal_reference.clone(),
251 force_open: Some(true),
252 currency_code,
253 }
254 }
255
256 #[allow(clippy::ptr_arg)]
276 pub fn buy_option_to_market(
277 epic: &String,
278 size: &f64,
279 expiry: &Option<String>,
280 deal_reference: &Option<String>,
281 currency_code: &Option<String>,
282 ) -> Self {
283 let rounded_size = (size * 100.0).floor() / 100.0;
284 let currency_code = if let Some(code) = currency_code {
285 Some(code.clone())
286 } else {
287 Some("EUR".to_string())
288 };
289 Self {
290 epic: epic.clone(),
291 direction: Direction::Buy,
292 size: rounded_size,
293 order_type: OrderType::Limit,
294 time_in_force: TimeInForce::FillOrKill,
295 level: Some(DEFAULT_ORDER_BUY_SIZE),
296 guaranteed_stop: Some(false),
297 stop_level: None,
298 stop_distance: None,
299 limit_level: None,
300 limit_distance: None,
301 expiry: expiry.clone(),
302 deal_reference: deal_reference.clone(),
303 force_open: Some(true),
304 currency_code: currency_code.clone(),
305 }
306 }
307
308 pub fn with_stop_loss(mut self, stop_level: f64) -> Self {
310 self.stop_level = Some(stop_level);
311 self
312 }
313
314 pub fn with_take_profit(mut self, limit_level: f64) -> Self {
316 self.limit_level = Some(limit_level);
317 self
318 }
319
320 pub fn with_reference(mut self, reference: String) -> Self {
322 self.deal_reference = Some(reference);
323 self
324 }
325}
326
327#[derive(Debug, Clone, Deserialize)]
329pub struct CreateOrderResponse {
330 #[serde(rename = "dealReference")]
332 pub deal_reference: String,
333}
334
335fn deserialize_nullable_status<'de, D>(deserializer: D) -> Result<Status, D::Error>
338where
339 D: Deserializer<'de>,
340{
341 let opt = Option::deserialize(deserializer)?;
342 Ok(opt.unwrap_or(Status::Rejected))
343}
344
345#[derive(Debug, Clone, Deserialize)]
347pub struct OrderConfirmation {
348 pub date: String,
350 #[serde(deserialize_with = "deserialize_nullable_status")]
353 pub status: Status,
354 pub reason: Option<String>,
356 #[serde(rename = "dealId")]
358 pub deal_id: Option<String>,
359 #[serde(rename = "dealReference")]
361 pub deal_reference: String,
362 #[serde(rename = "dealStatus")]
364 pub deal_status: Option<String>,
365 pub epic: Option<String>,
367 #[serde(rename = "expiry")]
369 pub expiry: Option<String>,
370 #[serde(rename = "guaranteedStop")]
372 pub guaranteed_stop: Option<bool>,
373 #[serde(rename = "level")]
375 pub level: Option<f64>,
376 #[serde(rename = "limitDistance")]
378 pub limit_distance: Option<f64>,
379 #[serde(rename = "limitLevel")]
381 pub limit_level: Option<f64>,
382 pub size: Option<f64>,
384 #[serde(rename = "stopDistance")]
386 pub stop_distance: Option<f64>,
387 #[serde(rename = "stopLevel")]
389 pub stop_level: Option<f64>,
390 #[serde(rename = "trailingStop")]
392 pub trailing_stop: Option<bool>,
393 pub direction: Option<Direction>,
395}
396
397#[derive(Debug, Clone, Serialize)]
399pub struct UpdatePositionRequest {
400 #[serde(rename = "stopLevel", skip_serializing_if = "Option::is_none")]
402 pub stop_level: Option<f64>,
403 #[serde(rename = "limitLevel", skip_serializing_if = "Option::is_none")]
405 pub limit_level: Option<f64>,
406 #[serde(rename = "trailingStop", skip_serializing_if = "Option::is_none")]
408 pub trailing_stop: Option<bool>,
409 #[serde(
411 rename = "trailingStopDistance",
412 skip_serializing_if = "Option::is_none"
413 )]
414 pub trailing_stop_distance: Option<f64>,
415}
416
417#[derive(Debug, Clone, Serialize)]
419pub struct ClosePositionRequest {
420 #[serde(rename = "dealId")]
422 pub deal_id: Option<String>,
423 pub direction: Direction,
425 pub size: f64,
427 #[serde(rename = "orderType")]
429 pub order_type: OrderType,
430 #[serde(rename = "timeInForce")]
432 pub time_in_force: TimeInForce,
433 #[serde(rename = "level", skip_serializing_if = "Option::is_none")]
435 pub level: Option<f64>,
436 #[serde(rename = "expiry")]
438 pub expiry: Option<String>,
439 pub epic: Option<String>,
441
442 #[serde(rename = "quoteId")]
444 pub quote_id: Option<String>,
445}
446
447impl ClosePositionRequest {
448 pub fn market(deal_id: String, direction: Direction, size: f64) -> Self {
450 Self {
451 deal_id: Some(deal_id),
452 direction,
453 size,
454 order_type: OrderType::Market,
455 time_in_force: TimeInForce::FillOrKill,
456 level: None,
457 expiry: None,
458 epic: None,
459 quote_id: None,
460 }
461 }
462
463 pub fn limit(deal_id: String, direction: Direction, size: f64, level: f64) -> Self {
467 Self {
468 deal_id: Some(deal_id),
469 direction,
470 size,
471 order_type: OrderType::Limit,
472 time_in_force: TimeInForce::FillOrKill,
473 level: Some(level),
474 expiry: None,
475 epic: None,
476 quote_id: None,
477 }
478 }
479
480 pub fn close_option_to_market_by_id(deal_id: String, direction: Direction, size: f64) -> Self {
490 let level = match direction {
491 Direction::Buy => Some(DEFAULT_ORDER_BUY_SIZE),
492 Direction::Sell => Some(DEFAULT_ORDER_SELL_SIZE),
493 };
494 Self {
495 deal_id: Some(deal_id),
496 direction,
497 size,
498 order_type: OrderType::Limit,
499 time_in_force: TimeInForce::FillOrKill,
500 level,
501 expiry: None,
502 epic: None,
503 quote_id: None,
504 }
505 }
506
507 pub fn close_option_to_market_by_epic(
519 epic: String,
520 expiry: String,
521 direction: Direction,
522 size: f64,
523 ) -> Self {
524 let level = match direction {
525 Direction::Buy => Some(DEFAULT_ORDER_BUY_SIZE),
526 Direction::Sell => Some(DEFAULT_ORDER_SELL_SIZE),
527 };
528 Self {
529 deal_id: None,
530 direction,
531 size,
532 order_type: OrderType::Limit,
533 time_in_force: TimeInForce::FillOrKill,
534 level,
535 expiry: Some(expiry),
536 epic: Some(epic),
537 quote_id: None,
538 }
539 }
540}
541
542#[derive(Debug, Clone, Deserialize)]
544pub struct ClosePositionResponse {
545 #[serde(rename = "dealReference")]
547 pub deal_reference: String,
548}
549
550#[derive(Debug, Clone, Deserialize)]
552pub struct UpdatePositionResponse {
553 #[serde(rename = "dealReference")]
555 pub deal_reference: String,
556}
557
558#[derive(Debug, Clone, Serialize)]
560pub struct CreateWorkingOrderRequest {
561 pub epic: String,
563 pub direction: Direction,
565 pub size: f64,
567 pub level: f64,
569 #[serde(rename = "type")]
571 pub order_type: OrderType,
572 #[serde(rename = "timeInForce")]
574 pub time_in_force: TimeInForce,
575 #[serde(rename = "guaranteedStop", skip_serializing_if = "Option::is_none")]
577 pub guaranteed_stop: Option<bool>,
578 #[serde(rename = "stopLevel", skip_serializing_if = "Option::is_none")]
580 pub stop_level: Option<f64>,
581 #[serde(rename = "stopDistance", skip_serializing_if = "Option::is_none")]
583 pub stop_distance: Option<f64>,
584 #[serde(rename = "limitLevel", skip_serializing_if = "Option::is_none")]
586 pub limit_level: Option<f64>,
587 #[serde(rename = "limitDistance", skip_serializing_if = "Option::is_none")]
589 pub limit_distance: Option<f64>,
590 #[serde(rename = "goodTillDate", skip_serializing_if = "Option::is_none")]
592 pub good_till_date: Option<String>,
593 #[serde(rename = "dealReference", skip_serializing_if = "Option::is_none")]
595 pub deal_reference: Option<String>,
596 #[serde(rename = "currencyCode", skip_serializing_if = "Option::is_none")]
598 pub currency_code: Option<String>,
599}
600
601impl CreateWorkingOrderRequest {
602 pub fn limit(epic: String, direction: Direction, size: f64, level: f64) -> Self {
604 Self {
605 epic,
606 direction,
607 size,
608 level,
609 order_type: OrderType::Limit,
610 time_in_force: TimeInForce::GoodTillCancelled,
611 guaranteed_stop: None,
612 stop_level: None,
613 stop_distance: None,
614 limit_level: None,
615 limit_distance: None,
616 good_till_date: None,
617 deal_reference: None,
618 currency_code: None,
619 }
620 }
621
622 pub fn stop(epic: String, direction: Direction, size: f64, level: f64) -> Self {
624 Self {
625 epic,
626 direction,
627 size,
628 level,
629 order_type: OrderType::Stop,
630 time_in_force: TimeInForce::GoodTillCancelled,
631 guaranteed_stop: None,
632 stop_level: None,
633 stop_distance: None,
634 limit_level: None,
635 limit_distance: None,
636 good_till_date: None,
637 deal_reference: None,
638 currency_code: None,
639 }
640 }
641
642 pub fn with_stop_loss(mut self, stop_level: f64) -> Self {
644 self.stop_level = Some(stop_level);
645 self
646 }
647
648 pub fn with_take_profit(mut self, limit_level: f64) -> Self {
650 self.limit_level = Some(limit_level);
651 self
652 }
653
654 pub fn with_reference(mut self, reference: String) -> Self {
656 self.deal_reference = Some(reference);
657 self
658 }
659
660 pub fn expires_at(mut self, date: String) -> Self {
662 self.time_in_force = TimeInForce::GoodTillDate;
663 self.good_till_date = Some(date);
664 self
665 }
666}
667
668#[derive(Debug, Clone, Deserialize)]
670pub struct CreateWorkingOrderResponse {
671 #[serde(rename = "dealReference")]
673 pub deal_reference: String,
674}