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