1use crate::constants::{DEFAULT_ORDER_BUY_LEVEL, DEFAULT_ORDER_SELL_LEVEL};
7use crate::prelude::{Deserialize, Serialize, WorkingOrder};
8use crate::presentation::order::{Direction, OrderType, TimeInForce};
9use pretty_simple_display::{DebugPretty, DisplaySimple};
10use std::fmt;
11use std::fmt::{Debug, Display};
12
13#[derive(Clone, Default, Deserialize, Serialize)]
15pub struct RecentPricesRequest<'a> {
16 pub epic: &'a str,
18 pub resolution: Option<&'a str>,
20 pub from: Option<&'a str>,
22 pub to: Option<&'a str>,
24 pub max_points: Option<i32>,
26 pub page_size: Option<i32>,
28 pub page_number: Option<i32>,
30}
31
32impl<'a> RecentPricesRequest<'a> {
33 pub fn new(epic: &'a str) -> Self {
35 Self {
36 epic,
37 ..Default::default()
38 }
39 }
40
41 pub fn with_resolution(mut self, resolution: &'a str) -> Self {
43 self.resolution = Some(resolution);
44 self
45 }
46
47 pub fn with_from(mut self, from: &'a str) -> Self {
49 self.from = Some(from);
50 self
51 }
52
53 pub fn with_to(mut self, to: &'a str) -> Self {
55 self.to = Some(to);
56 self
57 }
58
59 pub fn with_max_points(mut self, max_points: i32) -> Self {
61 self.max_points = Some(max_points);
62 self
63 }
64
65 pub fn with_page_size(mut self, page_size: i32) -> Self {
67 self.page_size = Some(page_size);
68 self
69 }
70
71 pub fn with_page_number(mut self, page_number: i32) -> Self {
73 self.page_number = Some(page_number);
74 self
75 }
76}
77
78impl Display for RecentPricesRequest<'_> {
79 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
80 let json = serde_json::to_string(self).unwrap_or_else(|_| "Invalid JSON".to_string());
81 write!(f, "{}", json)
82 }
83}
84
85impl Debug for RecentPricesRequest<'_> {
86 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
87 let json =
88 serde_json::to_string_pretty(self).unwrap_or_else(|_| "Invalid JSON".to_string());
89 write!(f, "{}", json)
90 }
91}
92
93#[derive(DebugPretty, DisplaySimple, Clone, Serialize, Deserialize)]
95pub struct CreateOrderRequest {
96 pub epic: String,
98 pub direction: Direction,
100 pub size: f64,
102 #[serde(rename = "orderType")]
104 pub order_type: OrderType,
105 #[serde(rename = "timeInForce")]
107 pub time_in_force: TimeInForce,
108 #[serde(skip_serializing_if = "Option::is_none")]
110 pub level: Option<f64>,
111 #[serde(rename = "guaranteedStop")]
113 pub guaranteed_stop: bool,
114 #[serde(rename = "stopLevel", skip_serializing_if = "Option::is_none")]
116 pub stop_level: Option<f64>,
117 #[serde(rename = "stopDistance", skip_serializing_if = "Option::is_none")]
119 pub stop_distance: Option<f64>,
120 #[serde(rename = "limitLevel", skip_serializing_if = "Option::is_none")]
122 pub limit_level: Option<f64>,
123 #[serde(rename = "limitDistance", skip_serializing_if = "Option::is_none")]
125 pub limit_distance: Option<f64>,
126 #[serde(skip_serializing_if = "Option::is_none")]
128 pub expiry: Option<String>,
129 #[serde(rename = "dealReference", skip_serializing_if = "Option::is_none")]
131 pub deal_reference: Option<String>,
132 #[serde(rename = "forceOpen")]
134 pub force_open: bool,
135 #[serde(rename = "currencyCode")]
137 pub currency_code: String,
138 #[serde(rename = "quoteId", skip_serializing_if = "Option::is_none")]
140 pub quote_id: Option<String>,
141 #[serde(rename = "trailingStop", skip_serializing_if = "Option::is_none")]
143 pub trailing_stop: Option<bool>,
144 #[serde(
146 rename = "trailingStopIncrement",
147 skip_serializing_if = "Option::is_none"
148 )]
149 pub trailing_stop_increment: Option<f64>,
150}
151
152impl CreateOrderRequest {
153 pub fn market(
155 epic: String,
156 direction: Direction,
157 size: f64,
158 currency_code: Option<String>,
159 deal_reference: Option<String>,
160 ) -> Self {
161 let rounded_size = (size * 100.0).floor() / 100.0;
162
163 let currency_code = currency_code.unwrap_or_else(|| "EUR".to_string());
164
165 Self {
166 epic,
167 direction,
168 size: rounded_size,
169 order_type: OrderType::Market,
170 time_in_force: TimeInForce::FillOrKill,
171 level: None,
172 guaranteed_stop: false,
173 stop_level: None,
174 stop_distance: None,
175 limit_level: None,
176 limit_distance: None,
177 expiry: Some("-".to_string()),
178 deal_reference,
179 force_open: true,
180 currency_code,
181 quote_id: None,
182 trailing_stop: Some(false),
183 trailing_stop_increment: None,
184 }
185 }
186
187 pub fn limit(
189 epic: String,
190 direction: Direction,
191 size: f64,
192 level: f64,
193 currency_code: Option<String>,
194 deal_reference: Option<String>,
195 ) -> Self {
196 let rounded_size = (size * 100.0).floor() / 100.0;
197
198 let currency_code = currency_code.unwrap_or_else(|| "EUR".to_string());
199
200 Self {
201 epic,
202 direction,
203 size: rounded_size,
204 order_type: OrderType::Limit,
205 time_in_force: TimeInForce::GoodTillCancelled,
206 level: Some(level),
207 guaranteed_stop: false,
208 stop_level: None,
209 stop_distance: None,
210 limit_level: None,
211 limit_distance: None,
212 expiry: None,
213 deal_reference,
214 force_open: true,
215 currency_code,
216 quote_id: None,
217 trailing_stop: Some(false),
218 trailing_stop_increment: None,
219 }
220 }
221
222 pub fn sell_option_to_market(
255 epic: String,
256 size: f64,
257 expiry: Option<String>,
258 deal_reference: Option<String>,
259 currency_code: Option<String>,
260 ) -> Self {
261 let rounded_size = (size * 100.0).floor() / 100.0;
262
263 let currency_code = currency_code.unwrap_or_else(|| "EUR".to_string());
264
265 let deal_reference =
266 deal_reference.or_else(|| Some(nanoid::nanoid!(30, &nanoid::alphabet::SAFE)));
267
268 Self {
269 epic,
270 direction: Direction::Sell,
271 size: rounded_size,
272 order_type: OrderType::Limit,
273 time_in_force: TimeInForce::FillOrKill,
274 level: Some(DEFAULT_ORDER_SELL_LEVEL),
275 guaranteed_stop: false,
276 stop_level: None,
277 stop_distance: None,
278 limit_level: None,
279 limit_distance: None,
280 expiry: expiry.clone(),
281 deal_reference: deal_reference.clone(),
282 force_open: true,
283 currency_code,
284 quote_id: None,
285 trailing_stop: Some(false),
286 trailing_stop_increment: None,
287 }
288 }
289
290 pub fn sell_option_to_market_w_force(
323 epic: String,
324 size: f64,
325 expiry: Option<String>,
326 deal_reference: Option<String>,
327 currency_code: Option<String>,
328 force_open: bool, ) -> Self {
330 let rounded_size = (size * 100.0).floor() / 100.0;
331
332 let currency_code = currency_code.unwrap_or_else(|| "EUR".to_string());
333
334 let deal_reference =
335 deal_reference.or_else(|| Some(nanoid::nanoid!(30, &nanoid::alphabet::SAFE)));
336
337 Self {
338 epic,
339 direction: Direction::Sell,
340 size: rounded_size,
341 order_type: OrderType::Limit,
342 time_in_force: TimeInForce::FillOrKill,
343 level: Some(DEFAULT_ORDER_SELL_LEVEL),
344 guaranteed_stop: false,
345 stop_level: None,
346 stop_distance: None,
347 limit_level: None,
348 limit_distance: None,
349 expiry: expiry.clone(),
350 deal_reference: deal_reference.clone(),
351 force_open,
352 currency_code,
353 quote_id: None,
354 trailing_stop: Some(false),
355 trailing_stop_increment: None,
356 }
357 }
358
359 pub fn buy_option_to_market(
379 epic: String,
380 size: f64,
381 expiry: Option<String>,
382 deal_reference: Option<String>,
383 currency_code: Option<String>,
384 ) -> Self {
385 let rounded_size = (size * 100.0).floor() / 100.0;
386
387 let currency_code = currency_code.unwrap_or_else(|| "EUR".to_string());
388
389 let deal_reference =
390 deal_reference.or_else(|| Some(nanoid::nanoid!(30, &nanoid::alphabet::SAFE)));
391
392 Self {
393 epic,
394 direction: Direction::Buy,
395 size: rounded_size,
396 order_type: OrderType::Limit,
397 time_in_force: TimeInForce::FillOrKill,
398 level: Some(DEFAULT_ORDER_BUY_LEVEL),
399 guaranteed_stop: false,
400 stop_level: None,
401 stop_distance: None,
402 limit_level: None,
403 limit_distance: None,
404 expiry: expiry.clone(),
405 deal_reference: deal_reference.clone(),
406 force_open: true,
407 currency_code: currency_code.clone(),
408 quote_id: None,
409 trailing_stop: Some(false),
410 trailing_stop_increment: None,
411 }
412 }
413
414 pub fn buy_option_to_market_w_force(
442 epic: String,
443 size: f64,
444 expiry: Option<String>,
445 deal_reference: Option<String>,
446 currency_code: Option<String>,
447 force_open: bool,
448 ) -> Self {
449 let rounded_size = (size * 100.0).floor() / 100.0;
450
451 let currency_code = currency_code.unwrap_or_else(|| "EUR".to_string());
452
453 let deal_reference =
454 deal_reference.or_else(|| Some(nanoid::nanoid!(30, &nanoid::alphabet::SAFE)));
455
456 Self {
457 epic,
458 direction: Direction::Buy,
459 size: rounded_size,
460 order_type: OrderType::Limit,
461 time_in_force: TimeInForce::FillOrKill,
462 level: Some(DEFAULT_ORDER_BUY_LEVEL),
463 guaranteed_stop: false,
464 stop_level: None,
465 stop_distance: None,
466 limit_level: None,
467 limit_distance: None,
468 expiry: expiry.clone(),
469 deal_reference: deal_reference.clone(),
470 force_open,
471 currency_code: currency_code.clone(),
472 quote_id: None,
473 trailing_stop: Some(false),
474 trailing_stop_increment: None,
475 }
476 }
477
478 pub fn with_stop_loss(mut self, stop_level: f64) -> Self {
480 self.stop_level = Some(stop_level);
481 self
482 }
483
484 pub fn with_take_profit(mut self, limit_level: f64) -> Self {
486 self.limit_level = Some(limit_level);
487 self
488 }
489
490 pub fn with_trailing_stop_loss(mut self, trailing_stop_increment: f64) -> Self {
492 self.trailing_stop = Some(true);
493 self.trailing_stop_increment = Some(trailing_stop_increment);
494 self
495 }
496
497 pub fn with_reference(mut self, reference: String) -> Self {
499 self.deal_reference = Some(reference);
500 self
501 }
502
503 pub fn with_stop_distance(mut self, stop_distance: f64) -> Self {
505 self.stop_distance = Some(stop_distance);
506 self
507 }
508
509 pub fn with_limit_distance(mut self, limit_distance: f64) -> Self {
511 self.limit_distance = Some(limit_distance);
512 self
513 }
514
515 pub fn with_guaranteed_stop(mut self, guaranteed: bool) -> Self {
517 self.guaranteed_stop = guaranteed;
518 self
519 }
520}
521
522#[derive(DebugPretty, DisplaySimple, Clone, Serialize, Deserialize)]
524pub struct UpdatePositionRequest {
525 #[serde(rename = "stopLevel", skip_serializing_if = "Option::is_none")]
527 pub stop_level: Option<f64>,
528 #[serde(rename = "limitLevel", skip_serializing_if = "Option::is_none")]
530 pub limit_level: Option<f64>,
531 #[serde(rename = "trailingStop", skip_serializing_if = "Option::is_none")]
533 pub trailing_stop: Option<bool>,
534 #[serde(
536 rename = "trailingStopDistance",
537 skip_serializing_if = "Option::is_none"
538 )]
539 pub trailing_stop_distance: Option<f64>,
540}
541
542#[derive(DebugPretty, DisplaySimple, Clone, Serialize, Deserialize)]
544pub struct ClosePositionRequest {
545 #[serde(rename = "dealId", skip_serializing_if = "Option::is_none")]
547 pub deal_id: Option<String>,
548 pub direction: Direction,
550 #[serde(skip_serializing_if = "Option::is_none")]
552 pub epic: Option<String>,
553 #[serde(rename = "expiry", skip_serializing_if = "Option::is_none")]
555 pub expiry: Option<String>,
556 #[serde(rename = "level", skip_serializing_if = "Option::is_none")]
558 pub level: Option<f64>,
559 #[serde(rename = "orderType")]
561 pub order_type: OrderType,
562 #[serde(rename = "quoteId", skip_serializing_if = "Option::is_none")]
564 pub quote_id: Option<String>,
565 pub size: f64,
567 #[serde(rename = "timeInForce")]
569 pub time_in_force: TimeInForce,
570}
571
572impl ClosePositionRequest {
573 pub fn market(deal_id: String, direction: Direction, size: f64) -> Self {
575 Self {
576 deal_id: Some(deal_id),
577 direction,
578 size,
579 order_type: OrderType::Market,
580 time_in_force: TimeInForce::FillOrKill,
581 level: None,
582 expiry: None,
583 epic: None,
584 quote_id: None,
585 }
586 }
587
588 pub fn limit(deal_id: String, direction: Direction, size: f64, level: f64) -> Self {
592 Self {
593 deal_id: Some(deal_id),
594 direction,
595 size,
596 order_type: OrderType::Limit,
597 time_in_force: TimeInForce::FillOrKill,
598 level: Some(level),
599 expiry: None,
600 epic: None,
601 quote_id: None,
602 }
603 }
604
605 pub fn close_option_to_market_by_id(deal_id: String, direction: Direction, size: f64) -> Self {
615 let level = match direction {
618 Direction::Buy => Some(DEFAULT_ORDER_BUY_LEVEL),
619 Direction::Sell => Some(DEFAULT_ORDER_SELL_LEVEL),
620 };
621
622 Self {
623 deal_id: Some(deal_id),
624 direction,
625 size,
626 order_type: OrderType::Limit,
627 time_in_force: TimeInForce::FillOrKill,
628 level,
629 expiry: None,
630 epic: None,
631 quote_id: None,
632 }
633 }
634
635 pub fn close_option_to_market_by_epic(
647 epic: String,
648 expiry: String,
649 direction: Direction,
650 size: f64,
651 ) -> Self {
652 let level = match direction {
655 Direction::Buy => Some(DEFAULT_ORDER_BUY_LEVEL),
656 Direction::Sell => Some(DEFAULT_ORDER_SELL_LEVEL),
657 };
658
659 Self {
660 deal_id: None,
661 direction,
662 size,
663 order_type: OrderType::Limit,
664 time_in_force: TimeInForce::FillOrKill,
665 level,
666 expiry: Some(expiry),
667 epic: Some(epic),
668 quote_id: None,
669 }
670 }
671}
672
673#[derive(DebugPretty, DisplaySimple, Clone, Deserialize, Serialize, Default)]
675pub struct CreateWorkingOrderRequest {
676 pub epic: String,
678 pub direction: Direction,
680 pub size: f64,
682 pub level: f64,
684 #[serde(rename = "type")]
686 pub order_type: OrderType,
687 #[serde(rename = "timeInForce")]
689 pub time_in_force: TimeInForce,
690 #[serde(rename = "guaranteedStop", skip_serializing_if = "Option::is_none")]
692 pub guaranteed_stop: Option<bool>,
693 #[serde(rename = "stopLevel", skip_serializing_if = "Option::is_none")]
695 pub stop_level: Option<f64>,
696 #[serde(rename = "stopDistance", skip_serializing_if = "Option::is_none")]
698 pub stop_distance: Option<f64>,
699 #[serde(rename = "limitLevel", skip_serializing_if = "Option::is_none")]
701 pub limit_level: Option<f64>,
702 #[serde(rename = "limitDistance", skip_serializing_if = "Option::is_none")]
704 pub limit_distance: Option<f64>,
705 #[serde(rename = "goodTillDate", skip_serializing_if = "Option::is_none")]
707 pub good_till_date: Option<String>,
708 #[serde(rename = "dealReference", skip_serializing_if = "Option::is_none")]
710 pub deal_reference: Option<String>,
711 #[serde(rename = "currencyCode")]
713 pub currency_code: String,
714 pub expiry: String,
716}
717
718impl From<WorkingOrder> for CreateWorkingOrderRequest {
719 fn from(value: WorkingOrder) -> Self {
720 let data = value.working_order_data;
721 Self {
722 epic: data.epic,
723 direction: data.direction,
724 size: data.order_size,
725 level: data.order_level,
726 order_type: data.order_type,
727 time_in_force: data.time_in_force,
728 guaranteed_stop: Some(data.guaranteed_stop),
729 stop_level: data.stop_level,
730 stop_distance: data.stop_distance,
731 limit_level: data.limit_level,
732 limit_distance: data.limit_distance,
733 good_till_date: data.good_till_date,
734 deal_reference: data.deal_reference,
735 currency_code: data.currency_code,
736 expiry: value.market_data.expiry,
737 }
738 }
739}
740
741impl CreateWorkingOrderRequest {
742 pub fn limit(
744 epic: String,
745 direction: Direction,
746 size: f64,
747 level: f64,
748 currency_code: String,
749 expiry: String,
750 ) -> Self {
751 Self {
752 epic,
753 direction,
754 size,
755 level,
756 order_type: OrderType::Limit,
757 time_in_force: TimeInForce::GoodTillCancelled,
758 guaranteed_stop: Some(false),
759 stop_level: None,
760 stop_distance: None,
761 limit_level: None,
762 limit_distance: None,
763 good_till_date: None,
764 deal_reference: None,
765 currency_code,
766 expiry,
767 }
768 }
769
770 pub fn stop(
772 epic: String,
773 direction: Direction,
774 size: f64,
775 level: f64,
776 currency_code: String,
777 expiry: String,
778 ) -> Self {
779 Self {
780 epic,
781 direction,
782 size,
783 level,
784 order_type: OrderType::Stop,
785 time_in_force: TimeInForce::GoodTillCancelled,
786 guaranteed_stop: Some(false),
787 stop_level: None,
788 stop_distance: None,
789 limit_level: None,
790 limit_distance: None,
791 good_till_date: None,
792 deal_reference: None,
793 currency_code,
794 expiry,
795 }
796 }
797
798 pub fn with_stop_loss(mut self, stop_level: f64) -> Self {
800 self.stop_level = Some(stop_level);
801 self
802 }
803
804 pub fn with_take_profit(mut self, limit_level: f64) -> Self {
806 self.limit_level = Some(limit_level);
807 self
808 }
809
810 pub fn with_reference(mut self, reference: String) -> Self {
812 self.deal_reference = Some(reference);
813 self
814 }
815
816 pub fn expires_at(mut self, date: String) -> Self {
818 self.time_in_force = TimeInForce::GoodTillDate;
819 self.good_till_date = Some(date);
820 self
821 }
822}