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 Self {
231 epic: epic.clone(),
232 direction: Direction::Sell,
233 size: *size,
234 order_type: OrderType::Limit,
235 time_in_force: TimeInForce::FillOrKill,
236 level: Some(DEFAULT_ORDER_SELL_SIZE),
237 guaranteed_stop: Some(false),
238 stop_level: None,
239 stop_distance: None,
240 limit_level: None,
241 limit_distance: None,
242 expiry: expiry.clone(),
243 deal_reference: deal_reference.clone(),
244 force_open: Some(true),
245 currency_code: currency_code.clone(),
246 }
247 }
248
249 #[allow(clippy::ptr_arg)]
269 pub fn buy_option_to_market(
270 epic: &String,
271 size: &f64,
272 expiry: &Option<String>,
273 deal_reference: &Option<String>,
274 currency_code: &Option<String>,
275 ) -> Self {
276 Self {
277 epic: epic.clone(),
278 direction: Direction::Buy,
279 size: *size,
280 order_type: OrderType::Limit,
281 time_in_force: TimeInForce::FillOrKill,
282 level: Some(DEFAULT_ORDER_BUY_SIZE),
283 guaranteed_stop: Some(false),
284 stop_level: None,
285 stop_distance: None,
286 limit_level: None,
287 limit_distance: None,
288 expiry: expiry.clone(),
289 deal_reference: deal_reference.clone(),
290 force_open: Some(true),
291 currency_code: currency_code.clone(),
292 }
293 }
294
295 pub fn with_stop_loss(mut self, stop_level: f64) -> Self {
297 self.stop_level = Some(stop_level);
298 self
299 }
300
301 pub fn with_take_profit(mut self, limit_level: f64) -> Self {
303 self.limit_level = Some(limit_level);
304 self
305 }
306
307 pub fn with_reference(mut self, reference: String) -> Self {
309 self.deal_reference = Some(reference);
310 self
311 }
312}
313
314#[derive(Debug, Clone, Deserialize)]
316pub struct CreateOrderResponse {
317 #[serde(rename = "dealReference")]
319 pub deal_reference: String,
320}
321
322fn deserialize_nullable_status<'de, D>(deserializer: D) -> Result<Status, D::Error>
325where
326 D: Deserializer<'de>,
327{
328 let opt = Option::deserialize(deserializer)?;
329 Ok(opt.unwrap_or(Status::Rejected))
330}
331
332#[derive(Debug, Clone, Deserialize)]
334pub struct OrderConfirmation {
335 pub date: String,
337 #[serde(deserialize_with = "deserialize_nullable_status")]
340 pub status: Status,
341 pub reason: Option<String>,
343 #[serde(rename = "dealId")]
345 pub deal_id: Option<String>,
346 #[serde(rename = "dealReference")]
348 pub deal_reference: String,
349 #[serde(rename = "dealStatus")]
351 pub deal_status: Option<String>,
352 pub epic: Option<String>,
354 #[serde(rename = "expiry")]
356 pub expiry: Option<String>,
357 #[serde(rename = "guaranteedStop")]
359 pub guaranteed_stop: Option<bool>,
360 #[serde(rename = "level")]
362 pub level: Option<f64>,
363 #[serde(rename = "limitDistance")]
365 pub limit_distance: Option<f64>,
366 #[serde(rename = "limitLevel")]
368 pub limit_level: Option<f64>,
369 pub size: Option<f64>,
371 #[serde(rename = "stopDistance")]
373 pub stop_distance: Option<f64>,
374 #[serde(rename = "stopLevel")]
376 pub stop_level: Option<f64>,
377 #[serde(rename = "trailingStop")]
379 pub trailing_stop: Option<bool>,
380 pub direction: Option<Direction>,
382}
383
384#[derive(Debug, Clone, Serialize)]
386pub struct UpdatePositionRequest {
387 #[serde(rename = "stopLevel", skip_serializing_if = "Option::is_none")]
389 pub stop_level: Option<f64>,
390 #[serde(rename = "limitLevel", skip_serializing_if = "Option::is_none")]
392 pub limit_level: Option<f64>,
393 #[serde(rename = "trailingStop", skip_serializing_if = "Option::is_none")]
395 pub trailing_stop: Option<bool>,
396 #[serde(
398 rename = "trailingStopDistance",
399 skip_serializing_if = "Option::is_none"
400 )]
401 pub trailing_stop_distance: Option<f64>,
402}
403
404#[derive(Debug, Clone, Serialize)]
406pub struct ClosePositionRequest {
407 #[serde(rename = "dealId")]
409 pub deal_id: Option<String>,
410 pub direction: Direction,
412 pub size: f64,
414 #[serde(rename = "orderType")]
416 pub order_type: OrderType,
417 #[serde(rename = "timeInForce")]
419 pub time_in_force: TimeInForce,
420 #[serde(rename = "level", skip_serializing_if = "Option::is_none")]
422 pub level: Option<f64>,
423 #[serde(rename = "expiry")]
425 pub expiry: Option<String>,
426 pub epic: Option<String>,
428
429 #[serde(rename = "quoteId")]
431 pub quote_id: Option<String>,
432}
433
434impl ClosePositionRequest {
435 pub fn market(deal_id: String, direction: Direction, size: f64) -> Self {
437 Self {
438 deal_id: Some(deal_id),
439 direction,
440 size,
441 order_type: OrderType::Market,
442 time_in_force: TimeInForce::FillOrKill,
443 level: None,
444 expiry: None,
445 epic: None,
446 quote_id: None,
447 }
448 }
449
450 pub fn limit(deal_id: String, direction: Direction, size: f64, level: f64) -> Self {
454 Self {
455 deal_id: Some(deal_id),
456 direction,
457 size,
458 order_type: OrderType::Limit,
459 time_in_force: TimeInForce::FillOrKill,
460 level: Some(level),
461 expiry: None,
462 epic: None,
463 quote_id: None,
464 }
465 }
466
467 pub fn close_option_to_market_by_id(deal_id: String, direction: Direction, size: f64) -> Self {
477 let level = match direction {
478 Direction::Buy => Some(DEFAULT_ORDER_BUY_SIZE),
479 Direction::Sell => Some(DEFAULT_ORDER_SELL_SIZE),
480 };
481 Self {
482 deal_id: Some(deal_id),
483 direction,
484 size,
485 order_type: OrderType::Limit,
486 time_in_force: TimeInForce::FillOrKill,
487 level,
488 expiry: None,
489 epic: None,
490 quote_id: None,
491 }
492 }
493
494 pub fn close_option_to_market_by_epic(
506 epic: String,
507 expiry: String,
508 direction: Direction,
509 size: f64,
510 ) -> Self {
511 let level = match direction {
512 Direction::Buy => Some(DEFAULT_ORDER_BUY_SIZE),
513 Direction::Sell => Some(DEFAULT_ORDER_SELL_SIZE),
514 };
515 Self {
516 deal_id: None,
517 direction,
518 size,
519 order_type: OrderType::Limit,
520 time_in_force: TimeInForce::FillOrKill,
521 level,
522 expiry: Some(expiry),
523 epic: Some(epic),
524 quote_id: None,
525 }
526 }
527}
528
529#[derive(Debug, Clone, Deserialize)]
531pub struct ClosePositionResponse {
532 #[serde(rename = "dealReference")]
534 pub deal_reference: String,
535}
536
537#[derive(Debug, Clone, Deserialize)]
539pub struct UpdatePositionResponse {
540 #[serde(rename = "dealReference")]
542 pub deal_reference: String,
543}
544
545#[derive(Debug, Clone, Serialize)]
547pub struct CreateWorkingOrderRequest {
548 pub epic: String,
550 pub direction: Direction,
552 pub size: f64,
554 pub level: f64,
556 #[serde(rename = "type")]
558 pub order_type: OrderType,
559 #[serde(rename = "timeInForce")]
561 pub time_in_force: TimeInForce,
562 #[serde(rename = "guaranteedStop", skip_serializing_if = "Option::is_none")]
564 pub guaranteed_stop: Option<bool>,
565 #[serde(rename = "stopLevel", skip_serializing_if = "Option::is_none")]
567 pub stop_level: Option<f64>,
568 #[serde(rename = "stopDistance", skip_serializing_if = "Option::is_none")]
570 pub stop_distance: Option<f64>,
571 #[serde(rename = "limitLevel", skip_serializing_if = "Option::is_none")]
573 pub limit_level: Option<f64>,
574 #[serde(rename = "limitDistance", skip_serializing_if = "Option::is_none")]
576 pub limit_distance: Option<f64>,
577 #[serde(rename = "goodTillDate", skip_serializing_if = "Option::is_none")]
579 pub good_till_date: Option<String>,
580 #[serde(rename = "dealReference", skip_serializing_if = "Option::is_none")]
582 pub deal_reference: Option<String>,
583 #[serde(rename = "currencyCode", skip_serializing_if = "Option::is_none")]
585 pub currency_code: Option<String>,
586}
587
588impl CreateWorkingOrderRequest {
589 pub fn limit(epic: String, direction: Direction, size: f64, level: f64) -> Self {
591 Self {
592 epic,
593 direction,
594 size,
595 level,
596 order_type: OrderType::Limit,
597 time_in_force: TimeInForce::GoodTillCancelled,
598 guaranteed_stop: None,
599 stop_level: None,
600 stop_distance: None,
601 limit_level: None,
602 limit_distance: None,
603 good_till_date: None,
604 deal_reference: None,
605 currency_code: None,
606 }
607 }
608
609 pub fn stop(epic: String, direction: Direction, size: f64, level: f64) -> Self {
611 Self {
612 epic,
613 direction,
614 size,
615 level,
616 order_type: OrderType::Stop,
617 time_in_force: TimeInForce::GoodTillCancelled,
618 guaranteed_stop: None,
619 stop_level: None,
620 stop_distance: None,
621 limit_level: None,
622 limit_distance: None,
623 good_till_date: None,
624 deal_reference: None,
625 currency_code: None,
626 }
627 }
628
629 pub fn with_stop_loss(mut self, stop_level: f64) -> Self {
631 self.stop_level = Some(stop_level);
632 self
633 }
634
635 pub fn with_take_profit(mut self, limit_level: f64) -> Self {
637 self.limit_level = Some(limit_level);
638 self
639 }
640
641 pub fn with_reference(mut self, reference: String) -> Self {
643 self.deal_reference = Some(reference);
644 self
645 }
646
647 pub fn expires_at(mut self, date: String) -> Self {
649 self.time_in_force = TimeInForce::GoodTillDate;
650 self.good_till_date = Some(date);
651 self
652 }
653}
654
655#[derive(Debug, Clone, Deserialize)]
657pub struct CreateWorkingOrderResponse {
658 #[serde(rename = "dealReference")]
660 pub deal_reference: String,
661}