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