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}