ig_client/model/requests.rs
1/******************************************************************************
2 Author: Joaquín Béjar García
3 Email: jb@taunais.com
4 Date: 19/10/25
5******************************************************************************/
6use crate::constants::{DEFAULT_ORDER_BUY_LEVEL, DEFAULT_ORDER_SELL_LEVEL};
7use crate::prelude::{Deserialize, Serialize};
8use crate::presentation::order::{Direction, OrderType, TimeInForce};
9use pretty_simple_display::DisplaySimple;
10
11/// Parameters for getting recent prices (API v3)
12#[derive(Debug, Clone, Default, Deserialize, Serialize)]
13pub struct RecentPricesRequest<'a> {
14 /// Instrument epic
15 pub epic: &'a str,
16 /// Optional price resolution (default: MINUTE)
17 pub resolution: Option<&'a str>,
18 /// Optional start date time (yyyy-MM-dd'T'HH:mm:ss)
19 pub from: Option<&'a str>,
20 /// Optional end date time (yyyy-MM-dd'T'HH:mm:ss)
21 pub to: Option<&'a str>,
22 /// Optional max number of price points (default: 10)
23 pub max_points: Option<i32>,
24 /// Optional page size (default: 20, disable paging = 0)
25 pub page_size: Option<i32>,
26 /// Optional page number (default: 1)
27 pub page_number: Option<i32>,
28}
29
30impl<'a> RecentPricesRequest<'a> {
31 /// Create new parameters with just the epic (required field)
32 pub fn new(epic: &'a str) -> Self {
33 Self {
34 epic,
35 ..Default::default()
36 }
37 }
38
39 /// Set the resolution
40 pub fn with_resolution(mut self, resolution: &'a str) -> Self {
41 self.resolution = Some(resolution);
42 self
43 }
44
45 /// Set the from date
46 pub fn with_from(mut self, from: &'a str) -> Self {
47 self.from = Some(from);
48 self
49 }
50
51 /// Set the to date
52 pub fn with_to(mut self, to: &'a str) -> Self {
53 self.to = Some(to);
54 self
55 }
56
57 /// Set the max points
58 pub fn with_max_points(mut self, max_points: i32) -> Self {
59 self.max_points = Some(max_points);
60 self
61 }
62
63 /// Set the page size
64 pub fn with_page_size(mut self, page_size: i32) -> Self {
65 self.page_size = Some(page_size);
66 self
67 }
68
69 /// Set the page number
70 pub fn with_page_number(mut self, page_number: i32) -> Self {
71 self.page_number = Some(page_number);
72 self
73 }
74}
75
76/// Model for creating a new order
77#[derive(Debug, Clone, DisplaySimple, Serialize, Deserialize)]
78pub struct CreateOrderRequest {
79 /// Instrument EPIC identifier
80 pub epic: String,
81 /// Order direction (buy or sell)
82 pub direction: Direction,
83 /// Order size/quantity
84 pub size: f64,
85 /// Type of order (market, limit, etc.)
86 #[serde(rename = "orderType")]
87 pub order_type: OrderType,
88 /// Order duration (how long the order remains valid)
89 #[serde(rename = "timeInForce")]
90 pub time_in_force: TimeInForce,
91 /// Price level for limit orders
92 #[serde(skip_serializing_if = "Option::is_none")]
93 pub level: Option<f64>,
94 /// Whether to use a guaranteed stop
95 #[serde(rename = "guaranteedStop")]
96 pub guaranteed_stop: bool,
97 /// Price level for stop loss
98 #[serde(rename = "stopLevel", skip_serializing_if = "Option::is_none")]
99 pub stop_level: Option<f64>,
100 /// Stop loss distance
101 #[serde(rename = "stopDistance", skip_serializing_if = "Option::is_none")]
102 pub stop_distance: Option<f64>,
103 /// Price level for take profit
104 #[serde(rename = "limitLevel", skip_serializing_if = "Option::is_none")]
105 pub limit_level: Option<f64>,
106 /// Take profit distance
107 #[serde(rename = "limitDistance", skip_serializing_if = "Option::is_none")]
108 pub limit_distance: Option<f64>,
109 /// Expiry date for the order
110 #[serde(skip_serializing_if = "Option::is_none")]
111 pub expiry: Option<String>,
112 /// Client-generated reference for the deal
113 #[serde(rename = "dealReference", skip_serializing_if = "Option::is_none")]
114 pub deal_reference: Option<String>,
115 /// Whether to force open a new position
116 #[serde(rename = "forceOpen")]
117 pub force_open: bool,
118 /// Currency code for the order (e.g., "USD", "EUR")
119 #[serde(rename = "currencyCode")]
120 pub currency_code: String,
121 /// Quote identifier for the order
122 #[serde(rename = "quoteId", skip_serializing_if = "Option::is_none")]
123 pub quote_id: Option<String>,
124 /// Trailing stop enabled
125 #[serde(rename = "trailingStop", skip_serializing_if = "Option::is_none")]
126 pub trailing_stop: Option<bool>,
127 /// Trailing stop increment (only if trailingStop is true)
128 #[serde(
129 rename = "trailingStopIncrement",
130 skip_serializing_if = "Option::is_none"
131 )]
132 pub trailing_stop_increment: Option<f64>,
133}
134
135impl CreateOrderRequest {
136 /// Creates a new market order, typically used for CFD (Contract for Difference) accounts
137 pub fn market(
138 epic: String,
139 direction: Direction,
140 size: f64,
141 currency_code: Option<String>,
142 deal_reference: Option<String>,
143 ) -> Self {
144 let rounded_size = (size * 100.0).floor() / 100.0;
145
146 let currency_code = currency_code.unwrap_or_else(|| "EUR".to_string());
147
148 Self {
149 epic,
150 direction,
151 size: rounded_size,
152 order_type: OrderType::Market,
153 time_in_force: TimeInForce::FillOrKill,
154 level: None,
155 guaranteed_stop: false,
156 stop_level: None,
157 stop_distance: None,
158 limit_level: None,
159 limit_distance: None,
160 expiry: Some("-".to_string()),
161 deal_reference,
162 force_open: true,
163 currency_code,
164 quote_id: None,
165 trailing_stop: Some(false),
166 trailing_stop_increment: None,
167 }
168 }
169
170 /// Creates a new limit order, typically used for CFD (Contract for Difference) accounts
171 pub fn limit(
172 epic: String,
173 direction: Direction,
174 size: f64,
175 level: f64,
176 currency_code: Option<String>,
177 deal_reference: Option<String>,
178 ) -> Self {
179 let rounded_size = (size * 100.0).floor() / 100.0;
180
181 let currency_code = currency_code.unwrap_or_else(|| "EUR".to_string());
182
183 Self {
184 epic,
185 direction,
186 size: rounded_size,
187 order_type: OrderType::Limit,
188 time_in_force: TimeInForce::GoodTillCancelled,
189 level: Some(level),
190 guaranteed_stop: false,
191 stop_level: None,
192 stop_distance: None,
193 limit_level: None,
194 limit_distance: None,
195 expiry: None,
196 deal_reference,
197 force_open: true,
198 currency_code,
199 quote_id: None,
200 trailing_stop: Some(false),
201 trailing_stop_increment: None,
202 }
203 }
204
205 /// Creates a new instance of a market sell option with predefined parameters.
206 ///
207 /// This function sets up a sell option to the market for a given asset (`epic`)
208 /// with the specified size. It configures the order with default values
209 /// for attributes such as direction, order type, and time-in-force.
210 ///
211 /// # Parameters
212 /// - `epic`: A `String` that represents the epic (unique identifier or code) of the instrument
213 /// being traded.
214 /// - `size`: A `f64` value representing the size or quantity of the order.
215 ///
216 /// # Returns
217 /// An instance of `Self` (the type implementing this function), containing the specified
218 /// `epic` and `size`, along with default values for other parameters:
219 ///
220 /// - `direction`: Set to `Direction::Sell`.
221 /// - `order_type`: Set to `OrderType::Limit`.
222 /// - `time_in_force`: Set to `TimeInForce::FillOrKill`.
223 /// - `level`: Set to `Some(DEFAULT_ORDER_SELL_SIZE)`.
224 /// - `guaranteed_stop`: Set to `false`.
225 /// - `stop_level`: Set to `None`.
226 /// - `stop_distance`: Set to `None`.
227 /// - `limit_level`: Set to `None`.
228 /// - `limit_distance`: Set to `None`.
229 /// - `expiry`: Set based on input or `None`.
230 /// - `deal_reference`: Auto-generated if not provided.
231 /// - `force_open`: Set to `true`.
232 /// - `currency_code`: Defaults to `"EUR"` if not provided.
233 ///
234 /// Note that this function allows for minimal input (the instrument and size),
235 /// while other fields are provided default values. If further customization is required,
236 /// you can modify the returned instance as needed.
237 pub fn sell_option_to_market(
238 epic: String,
239 size: f64,
240 expiry: Option<String>,
241 deal_reference: Option<String>,
242 currency_code: Option<String>,
243 ) -> Self {
244 let rounded_size = (size * 100.0).floor() / 100.0;
245
246 let currency_code = currency_code.unwrap_or_else(|| "EUR".to_string());
247
248 let deal_reference =
249 deal_reference.or_else(|| Some(nanoid::nanoid!(30, &nanoid::alphabet::SAFE)));
250
251 Self {
252 epic,
253 direction: Direction::Sell,
254 size: rounded_size,
255 order_type: OrderType::Limit,
256 time_in_force: TimeInForce::FillOrKill,
257 level: Some(DEFAULT_ORDER_SELL_LEVEL),
258 guaranteed_stop: false,
259 stop_level: None,
260 stop_distance: None,
261 limit_level: None,
262 limit_distance: None,
263 expiry: expiry.clone(),
264 deal_reference: deal_reference.clone(),
265 force_open: true,
266 currency_code,
267 quote_id: None,
268 trailing_stop: Some(false),
269 trailing_stop_increment: None,
270 }
271 }
272
273 /// Constructs and returns a new instance of the `Self` struct representing a sell option
274 /// to the market with specific parameters for execution.
275 ///
276 /// # Parameters
277 /// - `epic`: A `String` that specifies the EPIC
278 /// (Exchanged Product Information Code) of the instrument for which the sell order is created.
279 /// - `size`: A `f64` that represents the size of the sell
280 /// order. The size is rounded to two decimal places.
281 /// - `expiry`: An optional `String` that indicates the expiry date or period for
282 /// the sell order. If `None`, no expiry date will be set for the order.
283 /// - `deal_reference`: An optional `String` that contains a reference or identifier
284 /// for the deal. Can be used for tracking purposes.
285 /// - `currency_code`: An optional `String` representing the currency code. Defaults
286 /// to `"EUR"` if not provided.
287 /// - `force_open`: A `bool` that specifies whether to force open the
288 /// position. When `true`, a new position is opened even if an existing position for the
289 /// same instrument and direction is available.
290 ///
291 /// # Returns
292 /// - `Self`: A new instance populated with the provided parameters, including the following default
293 /// properties:
294 /// - `direction`: Set to `Direction::Sell` to designate the sell operation.
295 /// - `order_type`: Set to `OrderType::Limit` to signify the type of the order.
296 /// - `time_in_force`: Set to `TimeInForce::FillOrKill` indicating the order should be fully
297 /// executed or canceled.
298 /// - `level`: Set to a constant value `DEFAULT_ORDER_SELL_SIZE`.
299 /// - `guaranteed_stop`: Set to `false`, indicating no guaranteed stop.
300 /// - Other optional levels/distance fields (`stop_level`, `stop_distance`, `limit_level`,
301 /// `limit_distance`): Set to `None` by default.
302 ///
303 /// # Notes
304 /// - The input `size` is automatically rounded down to two decimal places before being stored.
305 pub fn sell_option_to_market_w_force(
306 epic: String,
307 size: f64,
308 expiry: Option<String>,
309 deal_reference: Option<String>,
310 currency_code: Option<String>,
311 force_open: bool, // Compensate position if it is already open
312 ) -> Self {
313 let rounded_size = (size * 100.0).floor() / 100.0;
314
315 let currency_code = currency_code.unwrap_or_else(|| "EUR".to_string());
316
317 let deal_reference =
318 deal_reference.or_else(|| Some(nanoid::nanoid!(30, &nanoid::alphabet::SAFE)));
319
320 Self {
321 epic,
322 direction: Direction::Sell,
323 size: rounded_size,
324 order_type: OrderType::Limit,
325 time_in_force: TimeInForce::FillOrKill,
326 level: Some(DEFAULT_ORDER_SELL_LEVEL),
327 guaranteed_stop: false,
328 stop_level: None,
329 stop_distance: None,
330 limit_level: None,
331 limit_distance: None,
332 expiry: expiry.clone(),
333 deal_reference: deal_reference.clone(),
334 force_open,
335 currency_code,
336 quote_id: None,
337 trailing_stop: Some(false),
338 trailing_stop_increment: None,
339 }
340 }
341
342 /// Creates a new instance of an order to buy an option in the market with specified parameters.
343 ///
344 /// This method initializes an order with the following default values:
345 /// - `direction` is set to `Buy`.
346 /// - `order_type` is set to `Limit`.
347 /// - `time_in_force` is set to `FillOrKill`.
348 /// - `level` is set to `Some(DEFAULT_ORDER_BUY_SIZE)`.
349 /// - `force_open` is set to `true`.
350 /// Other optional parameters, such as stop levels, distances, expiry, and currency code, are left as `None`.
351 ///
352 /// # Parameters
353 /// - `epic` (`String`): The identifier for the market or instrument to trade.
354 /// - `size` (`f64`): The size or quantity of the order to be executed.
355 ///
356 /// # Returns
357 /// A new instance of `Self` that represents the configured buy option for the given market.
358 ///
359 /// # Note
360 /// Ensure the `epic` and `size` values provided are valid and match required market conditions.
361 pub fn buy_option_to_market(
362 epic: String,
363 size: f64,
364 expiry: Option<String>,
365 deal_reference: Option<String>,
366 currency_code: Option<String>,
367 ) -> Self {
368 let rounded_size = (size * 100.0).floor() / 100.0;
369
370 let currency_code = currency_code.unwrap_or_else(|| "EUR".to_string());
371
372 let deal_reference =
373 deal_reference.or_else(|| Some(nanoid::nanoid!(30, &nanoid::alphabet::SAFE)));
374
375 Self {
376 epic,
377 direction: Direction::Buy,
378 size: rounded_size,
379 order_type: OrderType::Limit,
380 time_in_force: TimeInForce::FillOrKill,
381 level: Some(DEFAULT_ORDER_BUY_LEVEL),
382 guaranteed_stop: false,
383 stop_level: None,
384 stop_distance: None,
385 limit_level: None,
386 limit_distance: None,
387 expiry: expiry.clone(),
388 deal_reference: deal_reference.clone(),
389 force_open: true,
390 currency_code: currency_code.clone(),
391 quote_id: None,
392 trailing_stop: Some(false),
393 trailing_stop_increment: None,
394 }
395 }
396
397 /// Constructs a new instance of an order to buy an option in the market with optional force_open behavior.
398 ///
399 /// # Parameters
400 ///
401 /// * `epic` - A `String` representing the unique identifier of the instrument to be traded.
402 /// * `size` - A `f64` value that represents the size of the order.
403 /// * `expiry` - An optional `String` representing the expiry date of the option.
404 /// * `deal_reference` - An optional `String` for the deal reference identifier.
405 /// * `currency_code` - An optional `String` representing the currency in which the order is denominated.
406 /// Defaults to "EUR" if not provided.
407 /// * `force_open` - A `bool` indicating whether to force open a new position regardless of existing positions.
408 ///
409 /// # Returns
410 ///
411 /// Returns a new instance of `Self`, representing the constructed order with the provided parameters.
412 ///
413 /// # Behavior
414 ///
415 /// * The size of the order will be rounded down to two decimal places for precision.
416 /// * If a `currency_code` is not provided, the default currency code "EUR" is used.
417 /// * Other parameters are directly mapped into the returned instance.
418 ///
419 /// # Notes
420 ///
421 /// * This function assumes that other order-related fields such as `level`, `stop_level`, `stop_distance`,
422 /// etc., are set to their defaults or require specific business logic, such as
423 /// `DEFAULT_ORDER_BUY_SIZE` for the initial buy size.
424 pub fn buy_option_to_market_w_force(
425 epic: String,
426 size: f64,
427 expiry: Option<String>,
428 deal_reference: Option<String>,
429 currency_code: Option<String>,
430 force_open: bool,
431 ) -> Self {
432 let rounded_size = (size * 100.0).floor() / 100.0;
433
434 let currency_code = currency_code.unwrap_or_else(|| "EUR".to_string());
435
436 let deal_reference =
437 deal_reference.or_else(|| Some(nanoid::nanoid!(30, &nanoid::alphabet::SAFE)));
438
439 Self {
440 epic,
441 direction: Direction::Buy,
442 size: rounded_size,
443 order_type: OrderType::Limit,
444 time_in_force: TimeInForce::FillOrKill,
445 level: Some(DEFAULT_ORDER_BUY_LEVEL),
446 guaranteed_stop: false,
447 stop_level: None,
448 stop_distance: None,
449 limit_level: None,
450 limit_distance: None,
451 expiry: expiry.clone(),
452 deal_reference: deal_reference.clone(),
453 force_open,
454 currency_code: currency_code.clone(),
455 quote_id: None,
456 trailing_stop: Some(false),
457 trailing_stop_increment: None,
458 }
459 }
460
461 /// Adds a stop loss to the order
462 pub fn with_stop_loss(mut self, stop_level: f64) -> Self {
463 self.stop_level = Some(stop_level);
464 self
465 }
466
467 /// Adds a take profit to the order
468 pub fn with_take_profit(mut self, limit_level: f64) -> Self {
469 self.limit_level = Some(limit_level);
470 self
471 }
472
473 /// Adds a trailing stop loss to the order
474 pub fn with_trailing_stop_loss(mut self, trailing_stop_increment: f64) -> Self {
475 self.trailing_stop = Some(true);
476 self.trailing_stop_increment = Some(trailing_stop_increment);
477 self
478 }
479
480 /// Adds a reference to the order
481 pub fn with_reference(mut self, reference: String) -> Self {
482 self.deal_reference = Some(reference);
483 self
484 }
485
486 /// Adds a stop distance to the order
487 pub fn with_stop_distance(mut self, stop_distance: f64) -> Self {
488 self.stop_distance = Some(stop_distance);
489 self
490 }
491
492 /// Adds a limit distance to the order
493 pub fn with_limit_distance(mut self, limit_distance: f64) -> Self {
494 self.limit_distance = Some(limit_distance);
495 self
496 }
497
498 /// Adds a guaranteed stop to the order
499 pub fn with_guaranteed_stop(mut self, guaranteed: bool) -> Self {
500 self.guaranteed_stop = guaranteed;
501 self
502 }
503}
504
505/// Model for updating an existing position
506#[derive(Debug, Clone, DisplaySimple, Serialize, Deserialize)]
507pub struct UpdatePositionRequest {
508 /// New price level for stop loss
509 #[serde(rename = "stopLevel", skip_serializing_if = "Option::is_none")]
510 pub stop_level: Option<f64>,
511 /// New price level for take profit
512 #[serde(rename = "limitLevel", skip_serializing_if = "Option::is_none")]
513 pub limit_level: Option<f64>,
514 /// Whether to enable trailing stop
515 #[serde(rename = "trailingStop", skip_serializing_if = "Option::is_none")]
516 pub trailing_stop: Option<bool>,
517 /// Distance for trailing stop
518 #[serde(
519 rename = "trailingStopDistance",
520 skip_serializing_if = "Option::is_none"
521 )]
522 pub trailing_stop_distance: Option<f64>,
523}
524
525/// Model for closing an existing position
526#[derive(Debug, Clone, DisplaySimple, Serialize, Deserialize)]
527pub struct ClosePositionRequest {
528 /// Unique identifier for the position to close
529 #[serde(rename = "dealId", skip_serializing_if = "Option::is_none")]
530 pub deal_id: Option<String>,
531 /// Direction of the closing order (opposite to the position)
532 pub direction: Direction,
533 /// Instrument EPIC identifier
534 #[serde(skip_serializing_if = "Option::is_none")]
535 pub epic: Option<String>,
536 /// Expiry date for the order
537 #[serde(rename = "expiry", skip_serializing_if = "Option::is_none")]
538 pub expiry: Option<String>,
539 /// Price level for limit close orders
540 #[serde(rename = "level", skip_serializing_if = "Option::is_none")]
541 pub level: Option<f64>,
542 /// Type of order to use for closing
543 #[serde(rename = "orderType")]
544 pub order_type: OrderType,
545 /// Quote identifier for the order, used for certain order types that require a specific quote
546 #[serde(rename = "quoteId", skip_serializing_if = "Option::is_none")]
547 pub quote_id: Option<String>,
548 /// Size/quantity to close
549 pub size: f64,
550 /// Order duration for the closing order
551 #[serde(rename = "timeInForce")]
552 pub time_in_force: TimeInForce,
553}
554
555impl ClosePositionRequest {
556 /// Creates a request to close a position at market price
557 pub fn market(deal_id: String, direction: Direction, size: f64) -> Self {
558 Self {
559 deal_id: Some(deal_id),
560 direction,
561 size,
562 order_type: OrderType::Market,
563 time_in_force: TimeInForce::FillOrKill,
564 level: None,
565 expiry: None,
566 epic: None,
567 quote_id: None,
568 }
569 }
570
571 /// Creates a request to close a position at a specific price level
572 ///
573 /// This is useful for instruments that don't support market orders
574 pub fn limit(deal_id: String, direction: Direction, size: f64, level: f64) -> Self {
575 Self {
576 deal_id: Some(deal_id),
577 direction,
578 size,
579 order_type: OrderType::Limit,
580 time_in_force: TimeInForce::FillOrKill,
581 level: Some(level),
582 expiry: None,
583 epic: None,
584 quote_id: None,
585 }
586 }
587
588 /// Creates a request to close an option position by deal ID using a limit order with predefined price levels
589 ///
590 /// This is specifically designed for options trading where market orders are not supported
591 /// and a limit order with a predefined price level is required based on the direction.
592 ///
593 /// # Arguments
594 /// * `deal_id` - The ID of the deal to close
595 /// * `direction` - The direction of the closing order (opposite of the position direction)
596 /// * `size` - The size of the position to close
597 pub fn close_option_to_market_by_id(deal_id: String, direction: Direction, size: f64) -> Self {
598 // For options, we need to use limit orders with appropriate levels
599 // Use reasonable levels based on direction to ensure fill while being accepted
600 let level = match direction {
601 Direction::Buy => Some(DEFAULT_ORDER_BUY_LEVEL),
602 Direction::Sell => Some(DEFAULT_ORDER_SELL_LEVEL),
603 };
604
605 Self {
606 deal_id: Some(deal_id),
607 direction,
608 size,
609 order_type: OrderType::Limit,
610 time_in_force: TimeInForce::FillOrKill,
611 level,
612 expiry: None,
613 epic: None,
614 quote_id: None,
615 }
616 }
617
618 /// Creates a request to close an option position by epic identifier using a limit order with predefined price levels
619 ///
620 /// This is specifically designed for options trading where market orders are not supported
621 /// and a limit order with a predefined price level is required based on the direction.
622 /// This method is used when the deal ID is not available but the epic and expiry are known.
623 ///
624 /// # Arguments
625 /// * `epic` - The epic identifier of the instrument
626 /// * `expiry` - The expiry date of the option
627 /// * `direction` - The direction of the closing order (opposite of the position direction)
628 /// * `size` - The size of the position to close
629 pub fn close_option_to_market_by_epic(
630 epic: String,
631 expiry: String,
632 direction: Direction,
633 size: f64,
634 ) -> Self {
635 // For options, we need to use limit orders with appropriate levels
636 // Use reasonable levels based on direction to ensure fill while being accepted
637 let level = match direction {
638 Direction::Buy => Some(DEFAULT_ORDER_BUY_LEVEL),
639 Direction::Sell => Some(DEFAULT_ORDER_SELL_LEVEL),
640 };
641
642 Self {
643 deal_id: None,
644 direction,
645 size,
646 order_type: OrderType::Limit,
647 time_in_force: TimeInForce::FillOrKill,
648 level,
649 expiry: Some(expiry),
650 epic: Some(epic),
651 quote_id: None,
652 }
653 }
654}
655
656/// Model for creating a new working order
657#[derive(Debug, Clone, DisplaySimple, Deserialize, Serialize, Default)]
658pub struct CreateWorkingOrderRequest {
659 /// Instrument EPIC identifier
660 pub epic: String,
661 /// Order direction (buy or sell)
662 pub direction: Direction,
663 /// Order size/quantity
664 pub size: f64,
665 /// Price level for the order
666 pub level: f64,
667 /// Type of working order (LIMIT or STOP)
668 #[serde(rename = "type")]
669 pub order_type: OrderType,
670 /// Order duration (how long the order remains valid)
671 #[serde(rename = "timeInForce")]
672 pub time_in_force: TimeInForce,
673 /// Whether to use a guaranteed stop
674 #[serde(rename = "guaranteedStop", skip_serializing_if = "Option::is_none")]
675 pub guaranteed_stop: Option<bool>,
676 /// Price level for stop loss
677 #[serde(rename = "stopLevel", skip_serializing_if = "Option::is_none")]
678 pub stop_level: Option<f64>,
679 /// Distance for stop loss
680 #[serde(rename = "stopDistance", skip_serializing_if = "Option::is_none")]
681 pub stop_distance: Option<f64>,
682 /// Price level for take profit
683 #[serde(rename = "limitLevel", skip_serializing_if = "Option::is_none")]
684 pub limit_level: Option<f64>,
685 /// Distance for take profit
686 #[serde(rename = "limitDistance", skip_serializing_if = "Option::is_none")]
687 pub limit_distance: Option<f64>,
688 /// Expiry date for GTD orders
689 #[serde(rename = "goodTillDate", skip_serializing_if = "Option::is_none")]
690 pub good_till_date: Option<String>,
691 /// Client-generated reference for the deal
692 #[serde(rename = "dealReference", skip_serializing_if = "Option::is_none")]
693 pub deal_reference: Option<String>,
694 /// Currency code for the order (e.g., "USD", "EUR")
695 #[serde(rename = "currencyCode", skip_serializing_if = "Option::is_none")]
696 pub currency_code: Option<String>,
697}
698
699impl CreateWorkingOrderRequest {
700 /// Creates a new limit working order
701 pub fn limit(epic: String, direction: Direction, size: f64, level: f64) -> Self {
702 Self {
703 epic,
704 direction,
705 size,
706 level,
707 order_type: OrderType::Limit,
708 time_in_force: TimeInForce::GoodTillCancelled,
709 guaranteed_stop: None,
710 stop_level: None,
711 stop_distance: None,
712 limit_level: None,
713 limit_distance: None,
714 good_till_date: None,
715 deal_reference: None,
716 currency_code: None,
717 }
718 }
719
720 /// Creates a new stop working order
721 pub fn stop(epic: String, direction: Direction, size: f64, level: f64) -> Self {
722 Self {
723 epic,
724 direction,
725 size,
726 level,
727 order_type: OrderType::Stop,
728 time_in_force: TimeInForce::GoodTillCancelled,
729 guaranteed_stop: None,
730 stop_level: None,
731 stop_distance: None,
732 limit_level: None,
733 limit_distance: None,
734 good_till_date: None,
735 deal_reference: None,
736 currency_code: None,
737 }
738 }
739
740 /// Adds a stop loss to the working order
741 pub fn with_stop_loss(mut self, stop_level: f64) -> Self {
742 self.stop_level = Some(stop_level);
743 self
744 }
745
746 /// Adds a take profit to the working order
747 pub fn with_take_profit(mut self, limit_level: f64) -> Self {
748 self.limit_level = Some(limit_level);
749 self
750 }
751
752 /// Adds a reference to the working order
753 pub fn with_reference(mut self, reference: String) -> Self {
754 self.deal_reference = Some(reference);
755 self
756 }
757
758 /// Sets the order to expire at a specific date
759 pub fn expires_at(mut self, date: String) -> Self {
760 self.time_in_force = TimeInForce::GoodTillDate;
761 self.good_till_date = Some(date);
762 self
763 }
764}