ig_client/application/models/
order.rs

1/******************************************************************************
2   Author: Joaquín Béjar García
3   Email: jb@taunais.com
4   Date: 13/5/25
5******************************************************************************/
6use serde::{Deserialize, Serialize};
7
8/// Order direction (buy or sell)
9#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
10#[serde(rename_all = "UPPERCASE")]
11pub enum Direction {
12    /// Buy direction (long position)
13    Buy,
14    /// Sell direction (short position)
15    Sell,
16}
17
18/// Order type
19#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
20#[serde(rename_all = "UPPERCASE")]
21pub enum OrderType {
22    /// Limit order - executed when price reaches specified level
23    Limit,
24    /// Market order - executed immediately at current market price
25    Market,
26    /// Quote order - executed at quoted price
27    Quote,
28    /// Stop order - becomes market order when price reaches specified level
29    Stop,
30    /// Stop limit order - becomes limit order when price reaches specified level
31    StopLimit,
32}
33
34/// Order status
35#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
36#[serde(rename_all = "UPPERCASE")]
37pub enum OrderStatus {
38    /// Order has been accepted by the system
39    Accepted,
40    /// Order has been rejected by the system
41    Rejected,
42    /// Order is currently working (waiting to be filled)
43    Working,
44    /// Order has been filled (executed)
45    Filled,
46    /// Order has been cancelled
47    Cancelled,
48    /// Order has expired (time in force elapsed)
49    Expired,
50}
51
52/// Order duration (time in force)
53#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
54pub enum TimeInForce {
55    /// Order remains valid until cancelled by the client
56    #[serde(rename = "GOOD_TILL_CANCELLED")]
57    GoodTillCancelled,
58    /// Order remains valid until a specified date
59    #[serde(rename = "GOOD_TILL_DATE")]
60    GoodTillDate,
61    /// Order is executed immediately (partially or completely) or cancelled
62    #[serde(rename = "IMMEDIATE_OR_CANCEL")]
63    ImmediateOrCancel,
64    /// Order must be filled completely immediately or cancelled
65    #[serde(rename = "FILL_OR_KILL")]
66    FillOrKill,
67}
68
69/// Model for creating a new order
70#[derive(Debug, Clone, Serialize)]
71pub struct CreateOrderRequest {
72    /// Instrument EPIC identifier
73    pub epic: String,
74    /// Order direction (buy or sell)
75    pub direction: Direction,
76    /// Order size/quantity
77    pub size: f64,
78    /// Type of order (market, limit, etc.)
79    #[serde(rename = "orderType")]
80    pub order_type: OrderType,
81    /// Order duration (how long the order remains valid)
82    #[serde(rename = "timeInForce")]
83    pub time_in_force: TimeInForce,
84    /// Price level for limit orders
85    #[serde(rename = "level", skip_serializing_if = "Option::is_none")]
86    pub level: Option<f64>,
87    /// Whether to use a guaranteed stop
88    #[serde(rename = "guaranteedStop", skip_serializing_if = "Option::is_none")]
89    pub guaranteed_stop: Option<bool>,
90    /// Price level for stop loss
91    #[serde(rename = "stopLevel", skip_serializing_if = "Option::is_none")]
92    pub stop_level: Option<f64>,
93    /// Distance for stop loss
94    #[serde(rename = "stopDistance", skip_serializing_if = "Option::is_none")]
95    pub stop_distance: Option<f64>,
96    /// Price level for take profit
97    #[serde(rename = "limitLevel", skip_serializing_if = "Option::is_none")]
98    pub limit_level: Option<f64>,
99    /// Distance for take profit
100    #[serde(rename = "limitDistance", skip_serializing_if = "Option::is_none")]
101    pub limit_distance: Option<f64>,
102    /// Expiry date for the order
103    #[serde(rename = "expiry", skip_serializing_if = "Option::is_none")]
104    pub expiry: Option<String>,
105    /// Client-generated reference for the deal
106    #[serde(rename = "dealReference", skip_serializing_if = "Option::is_none")]
107    pub deal_reference: Option<String>,
108    /// Whether to force open a new position
109    #[serde(rename = "forceOpen", skip_serializing_if = "Option::is_none")]
110    pub force_open: Option<bool>,
111}
112
113impl CreateOrderRequest {
114    /// Creates a new market order
115    pub fn market(epic: String, direction: Direction, size: f64) -> Self {
116        Self {
117            epic,
118            direction,
119            size,
120            order_type: OrderType::Market,
121            time_in_force: TimeInForce::FillOrKill,
122            level: None,
123            guaranteed_stop: None,
124            stop_level: None,
125            stop_distance: None,
126            limit_level: None,
127            limit_distance: None,
128            expiry: None,
129            deal_reference: None,
130            force_open: Some(true),
131        }
132    }
133
134    /// Creates a new limit order
135    pub fn limit(epic: String, direction: Direction, size: f64, level: f64) -> Self {
136        Self {
137            epic,
138            direction,
139            size,
140            order_type: OrderType::Limit,
141            time_in_force: TimeInForce::GoodTillCancelled,
142            level: Some(level),
143            guaranteed_stop: None,
144            stop_level: None,
145            stop_distance: None,
146            limit_level: None,
147            limit_distance: None,
148            expiry: None,
149            deal_reference: None,
150            force_open: Some(true),
151        }
152    }
153
154    /// Adds a stop loss to the order
155    pub fn with_stop_loss(mut self, stop_level: f64) -> Self {
156        self.stop_level = Some(stop_level);
157        self
158    }
159
160    /// Adds a take profit to the order
161    pub fn with_take_profit(mut self, limit_level: f64) -> Self {
162        self.limit_level = Some(limit_level);
163        self
164    }
165
166    /// Adds a reference to the order
167    pub fn with_reference(mut self, reference: String) -> Self {
168        self.deal_reference = Some(reference);
169        self
170    }
171}
172
173/// Response to order creation
174#[derive(Debug, Clone, Deserialize)]
175pub struct CreateOrderResponse {
176    /// Client-generated reference for the deal
177    #[serde(rename = "dealReference")]
178    pub deal_reference: String,
179}
180
181/// Details of a confirmed order
182#[derive(Debug, Clone, Deserialize)]
183pub struct OrderConfirmation {
184    /// Date and time of the confirmation
185    pub date: String,
186    /// Status of the order (accepted, rejected, etc.)
187    pub status: OrderStatus,
188    /// Reason for rejection if applicable
189    pub reason: Option<String>,
190    /// Unique identifier for the deal
191    #[serde(rename = "dealId")]
192    pub deal_id: Option<String>,
193    /// Client-generated reference for the deal
194    #[serde(rename = "dealReference")]
195    pub deal_reference: String,
196    /// Status of the deal
197    #[serde(rename = "dealStatus")]
198    pub deal_status: Option<String>,
199    /// Instrument EPIC identifier
200    pub epic: Option<String>,
201    /// Expiry date for the order
202    #[serde(rename = "expiry")]
203    pub expiry: Option<String>,
204    /// Whether a guaranteed stop was used
205    #[serde(rename = "guaranteedStop")]
206    pub guaranteed_stop: Option<bool>,
207    /// Price level of the order
208    #[serde(rename = "level")]
209    pub level: Option<f64>,
210    /// Distance for take profit
211    #[serde(rename = "limitDistance")]
212    pub limit_distance: Option<f64>,
213    /// Price level for take profit
214    #[serde(rename = "limitLevel")]
215    pub limit_level: Option<f64>,
216    /// Size/quantity of the order
217    pub size: Option<f64>,
218    /// Distance for stop loss
219    #[serde(rename = "stopDistance")]
220    pub stop_distance: Option<f64>,
221    /// Price level for stop loss
222    #[serde(rename = "stopLevel")]
223    pub stop_level: Option<f64>,
224    /// Whether a trailing stop was used
225    #[serde(rename = "trailingStop")]
226    pub trailing_stop: Option<bool>,
227    /// Direction of the order (buy or sell)
228    pub direction: Option<Direction>,
229}
230
231/// Model for updating an existing position
232#[derive(Debug, Clone, Serialize)]
233pub struct UpdatePositionRequest {
234    /// New price level for stop loss
235    #[serde(rename = "stopLevel", skip_serializing_if = "Option::is_none")]
236    pub stop_level: Option<f64>,
237    /// New price level for take profit
238    #[serde(rename = "limitLevel", skip_serializing_if = "Option::is_none")]
239    pub limit_level: Option<f64>,
240    /// Whether to enable trailing stop
241    #[serde(rename = "trailingStop", skip_serializing_if = "Option::is_none")]
242    pub trailing_stop: Option<bool>,
243    /// Distance for trailing stop
244    #[serde(
245        rename = "trailingStopDistance",
246        skip_serializing_if = "Option::is_none"
247    )]
248    pub trailing_stop_distance: Option<f64>,
249}
250
251/// Model for closing an existing position
252#[derive(Debug, Clone, Serialize)]
253pub struct ClosePositionRequest {
254    /// Unique identifier for the position to close
255    #[serde(rename = "dealId")]
256    pub deal_id: String,
257    /// Direction of the closing order (opposite to the position)
258    pub direction: Direction,
259    /// Size/quantity to close
260    pub size: f64,
261    /// Type of order to use for closing
262    #[serde(rename = "orderType")]
263    pub order_type: OrderType,
264    /// Order duration for the closing order
265    #[serde(rename = "timeInForce")]
266    pub time_in_force: TimeInForce,
267    /// Price level for limit close orders
268    #[serde(rename = "level", skip_serializing_if = "Option::is_none")]
269    pub level: Option<f64>,
270}
271
272impl ClosePositionRequest {
273    /// Creates a request to close a position at market price
274    pub fn market(deal_id: String, direction: Direction, size: f64) -> Self {
275        Self {
276            deal_id,
277            direction,
278            size,
279            order_type: OrderType::Market,
280            time_in_force: TimeInForce::FillOrKill,
281            level: None,
282        }
283    }
284}
285
286/// Response to closing a position
287#[derive(Debug, Clone, Deserialize)]
288pub struct ClosePositionResponse {
289    /// Client-generated reference for the closing deal
290    #[serde(rename = "dealReference")]
291    pub deal_reference: String,
292}