Skip to main content

ibapi/orders/builder/
types.rs

1use serde::{Deserialize, Serialize};
2use std::fmt;
3
4/// Represents a unique order identifier
5#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
6#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
7pub struct OrderId(pub i32);
8
9impl OrderId {
10    /// Creates a new OrderId
11    pub fn new(id: i32) -> Self {
12        Self(id)
13    }
14
15    /// Returns the inner i32 value
16    pub fn value(&self) -> i32 {
17        self.0
18    }
19}
20
21impl fmt::Display for OrderId {
22    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
23        write!(f, "{}", self.0)
24    }
25}
26
27impl From<i32> for OrderId {
28    fn from(id: i32) -> Self {
29        Self(id)
30    }
31}
32
33impl From<OrderId> for i32 {
34    fn from(id: OrderId) -> i32 {
35        id.0
36    }
37}
38
39/// Represents the order IDs for a bracket order
40#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
41#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
42pub struct BracketOrderIds {
43    /// The parent order ID
44    pub parent: OrderId,
45    /// The take profit order ID
46    pub take_profit: OrderId,
47    /// The stop loss order ID
48    pub stop_loss: OrderId,
49}
50
51impl BracketOrderIds {
52    /// Creates a new BracketOrderIds
53    pub fn new(parent: i32, take_profit: i32, stop_loss: i32) -> Self {
54        Self {
55            parent: OrderId(parent),
56            take_profit: OrderId(take_profit),
57            stop_loss: OrderId(stop_loss),
58        }
59    }
60
61    /// Returns all order IDs as a vector
62    pub fn as_vec(&self) -> Vec<OrderId> {
63        vec![self.parent, self.take_profit, self.stop_loss]
64    }
65
66    /// Returns all order IDs as i32 values
67    pub fn as_i32_vec(&self) -> Vec<i32> {
68        vec![self.parent.0, self.take_profit.0, self.stop_loss.0]
69    }
70}
71
72impl fmt::Display for BracketOrderIds {
73    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
74        write!(
75            f,
76            "BracketOrder(parent: {}, tp: {}, sl: {})",
77            self.parent, self.take_profit, self.stop_loss
78        )
79    }
80}
81
82impl From<Vec<i32>> for BracketOrderIds {
83    fn from(ids: Vec<i32>) -> Self {
84        assert_eq!(ids.len(), 3, "BracketOrderIds requires exactly 3 order IDs");
85        Self::new(ids[0], ids[1], ids[2])
86    }
87}
88
89impl From<[i32; 3]> for BracketOrderIds {
90    fn from(ids: [i32; 3]) -> Self {
91        Self::new(ids[0], ids[1], ids[2])
92    }
93}
94
95/// Represents a quantity of shares/contracts
96#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
97#[derive(Debug, Clone, Copy, PartialEq, PartialOrd, Serialize, Deserialize)]
98pub struct Quantity(f64);
99
100impl Quantity {
101    /// Create a validated quantity ensuring it is positive and finite.
102    pub fn new(value: f64) -> Result<Self, ValidationError> {
103        if value <= 0.0 {
104            return Err(ValidationError::InvalidQuantity(value));
105        }
106        if value.is_nan() || value.is_infinite() {
107            return Err(ValidationError::InvalidQuantity(value));
108        }
109        Ok(Self(value))
110    }
111
112    /// Access the raw quantity value.
113    pub fn value(&self) -> f64 {
114        self.0
115    }
116}
117
118/// Represents a price value
119#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
120#[derive(Debug, Clone, Copy, PartialEq, PartialOrd, Serialize, Deserialize)]
121pub struct Price(f64);
122
123impl Price {
124    /// Create a validated price ensuring it is finite.
125    pub fn new(value: f64) -> Result<Self, ValidationError> {
126        if value.is_nan() || value.is_infinite() {
127            return Err(ValidationError::InvalidPrice(value));
128        }
129        Ok(Self(value))
130    }
131
132    /// Access the raw price value.
133    pub fn value(&self) -> f64 {
134        self.0
135    }
136}
137
138/// Time in force options
139#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
140#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
141pub enum TimeInForce {
142    /// Order is active only for the current trading day.
143    Day,
144    /// Order remains active until cancelled.
145    GoodTillCancel,
146    /// Order must be filled immediately or cancelled.
147    ImmediateOrCancel,
148    /// Order remains active until the specified date (`YYYYMMDD`).
149    GoodTillDate {
150        /// Date at which the order expires.
151        date: String,
152    },
153    /// Order must be filled entirely or cancelled immediately.
154    FillOrKill,
155    /// Good-till-crossing (GTX) order type.
156    GoodTillCrossing,
157    /// Day-till-cancelled (DTC) order type.
158    DayTillCanceled,
159    /// Auction-only order.
160    Auction,
161    /// Opening auction order.
162    OpeningAuction,
163}
164
165impl TimeInForce {
166    /// Return the TWS API string identifier for the time-in-force.
167    pub fn as_str(&self) -> &str {
168        match self {
169            Self::Day => "DAY",
170            Self::GoodTillCancel => "GTC",
171            Self::ImmediateOrCancel => "IOC",
172            Self::GoodTillDate { .. } => "GTD",
173            Self::FillOrKill => "FOK",
174            Self::GoodTillCrossing => "GTX",
175            Self::DayTillCanceled => "DTC",
176            Self::Auction => "AUC",
177            Self::OpeningAuction => "OPG",
178        }
179    }
180}
181
182/// Auction type for auction orders
183#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
184#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
185pub enum AuctionType {
186    /// Opening auction strategy.
187    Opening,
188    /// Closing auction strategy.
189    Closing,
190    /// Volatility auction strategy.
191    Volatility,
192}
193
194impl AuctionType {
195    /// Return the numeric strategy identifier used by TWS.
196    pub fn to_strategy(&self) -> i32 {
197        match self {
198            Self::Opening => 1,
199            Self::Closing => 2,
200            Self::Volatility => 4,
201        }
202    }
203}
204
205/// Order types supported by Interactive Brokers
206#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
207#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
208pub enum OrderType {
209    // Basic Orders
210    /// Market order executed immediately at the best available price.
211    Market,
212    /// Limit order with a maximum/minimum execution price.
213    Limit,
214    /// Stop order that triggers a market order once the stop price is hit.
215    Stop,
216    /// Stop-limit order that triggers a limit order at the stop price.
217    StopLimit,
218
219    // Trailing Orders
220    /// Trailing stop order with a moving stop offset.
221    TrailingStop,
222    /// Trailing stop-limit order with both stop and limit offsets.
223    TrailingStopLimit,
224
225    // Time-based Orders
226    /// Market-on-close order.
227    MarketOnClose,
228    /// Limit-on-close order.
229    LimitOnClose,
230    /// Market-on-open order.
231    MarketOnOpen,
232    /// Limit-on-open order.
233    LimitOnOpen,
234    /// Auction order routed to an exchange auction.
235    AtAuction,
236
237    // Touched Orders
238    /// Market-if-touched order.
239    MarketIfTouched,
240    /// Limit-if-touched order.
241    LimitIfTouched,
242
243    // Protected Orders
244    /// Market order with price protection.
245    MarketWithProtection,
246    /// Stop order with price protection.
247    StopWithProtection,
248
249    // Market Variants
250    /// Market-to-limit order that becomes a limit order if not filled.
251    MarketToLimit,
252    /// Midprice order targeting the NBBO midpoint.
253    Midprice,
254
255    // Pegged Orders
256    /// Pegged-to-market order following the best quote.
257    PeggedToMarket,
258    /// Pegged-to-stock order for option hedging.
259    PeggedToStock,
260    /// Pegged-to-midpoint order tracking the midpoint.
261    PeggedToMidpoint,
262    /// Pegged-to-benchmark order using a benchmark price.
263    PeggedToBenchmark,
264    /// Peg to best order.
265    PegBest,
266
267    // Relative Orders
268    /// Relative (pegged) order offset from the best price.
269    Relative,
270    /// Passive relative order posting liquidity.
271    PassiveRelative,
272
273    // Special Orders
274    /// Volatility order for options.
275    Volatility,
276    /// Box-top order that converts to market at the best price.
277    BoxTop,
278    /// Auction limit order.
279    AuctionLimit,
280    /// Auction relative (pegged) order.
281    AuctionRelative,
282
283    // Combo Orders (special handling required)
284    /// Limit order for combo legs.
285    ComboLimit,
286    /// Market order for combo legs.
287    ComboMarket,
288    /// Relative + limit order for combo legs.
289    RelativeLimitCombo,
290    /// Relative + market order for combo legs.
291    RelativeMarketCombo,
292}
293
294impl OrderType {
295    /// Return the TWS API string identifier for this order type.
296    pub fn as_str(&self) -> &str {
297        match self {
298            // Basic Orders
299            Self::Market => "MKT",
300            Self::Limit => "LMT",
301            Self::Stop => "STP",
302            Self::StopLimit => "STP LMT",
303
304            // Trailing Orders
305            Self::TrailingStop => "TRAIL",
306            Self::TrailingStopLimit => "TRAIL LIMIT",
307
308            // Time-based Orders
309            Self::MarketOnClose => "MOC",
310            Self::LimitOnClose => "LOC",
311            Self::MarketOnOpen => "MKT",
312            Self::LimitOnOpen => "LMT",
313            Self::AtAuction => "MTL",
314
315            // Touched Orders
316            Self::MarketIfTouched => "MIT",
317            Self::LimitIfTouched => "LIT",
318
319            // Protected Orders
320            Self::MarketWithProtection => "MKT PRT",
321            Self::StopWithProtection => "STP PRT",
322
323            // Market Variants
324            Self::MarketToLimit => "MTL",
325            Self::Midprice => "MIDPRICE",
326
327            // Pegged Orders
328            Self::PeggedToMarket => "PEG MKT",
329            Self::PeggedToStock => "PEG STK",
330            Self::PeggedToMidpoint => "PEG MID",
331            Self::PeggedToBenchmark => "PEG BENCH",
332            Self::PegBest => "PEG BEST",
333
334            // Relative Orders
335            Self::Relative => "REL",
336            Self::PassiveRelative => "PASSV REL",
337
338            // Special Orders
339            Self::Volatility => "VOL",
340            Self::BoxTop => "BOX TOP",
341            Self::AuctionLimit => "LMT",
342            Self::AuctionRelative => "REL",
343
344            // Combo Orders
345            Self::ComboLimit => "LMT",
346            Self::ComboMarket => "MKT",
347            Self::RelativeLimitCombo => "REL + LMT",
348            Self::RelativeMarketCombo => "REL + MKT",
349        }
350    }
351
352    /// Returns true if this order type requires a limit price
353    pub fn requires_limit_price(&self) -> bool {
354        matches!(
355            self,
356            Self::Limit
357                | Self::StopLimit
358                | Self::LimitOnClose
359                | Self::LimitOnOpen
360                | Self::LimitIfTouched
361                | Self::AuctionLimit
362                | Self::ComboLimit
363                | Self::RelativeLimitCombo
364                | Self::AtAuction // TrailingStopLimit uses limit_price_offset, not limit_price
365        )
366    }
367
368    /// Returns true if this order type requires a stop/aux price
369    pub fn requires_aux_price(&self) -> bool {
370        matches!(
371            self,
372            Self::Stop
373                | Self::StopLimit
374                | Self::MarketIfTouched
375                | Self::LimitIfTouched
376                | Self::StopWithProtection
377                | Self::TrailingStop
378                | Self::TrailingStopLimit
379                | Self::Relative
380                | Self::PassiveRelative
381                | Self::AuctionRelative
382                | Self::PeggedToMarket
383        )
384    }
385}
386
387/// Validation errors
388#[derive(Debug, Clone, PartialEq)]
389pub enum ValidationError {
390    /// Quantity must be positive and finite.
391    InvalidQuantity(f64),
392    /// Price must be finite (not NaN or infinity).
393    InvalidPrice(f64),
394    /// Required builder field was not supplied.
395    MissingRequiredField(&'static str),
396    /// Combination of inputs violates broker rules.
397    InvalidCombination(String),
398    /// Stop price conflicts with current market context.
399    InvalidStopPrice {
400        /// Stop trigger price supplied by caller.
401        stop: f64,
402        /// Reference market price used for validation.
403        current: f64,
404    },
405    /// Limit price conflicts with current market context.
406    InvalidLimitPrice {
407        /// Limit price supplied by caller.
408        limit: f64,
409        /// Reference market price used for validation.
410        current: f64,
411    },
412    /// Bracket order configuration is invalid.
413    InvalidBracketOrder(String),
414    /// Percentage value outside allowed range (10-50%).
415    InvalidPercentage {
416        /// The field name (e.g., "max_pct_vol", "pct_vol").
417        field: &'static str,
418        /// The invalid value provided.
419        value: f64,
420        /// Minimum allowed value.
421        min: f64,
422        /// Maximum allowed value.
423        max: f64,
424    },
425}
426
427impl fmt::Display for ValidationError {
428    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
429        match self {
430            Self::InvalidQuantity(q) => write!(f, "Invalid quantity: {}", q),
431            Self::InvalidPrice(p) => write!(f, "Invalid price: {}", p),
432            Self::MissingRequiredField(field) => write!(f, "Missing required field: {}", field),
433            Self::InvalidCombination(msg) => write!(f, "Invalid combination: {}", msg),
434            Self::InvalidStopPrice { stop, current } => {
435                write!(f, "Invalid stop price {} for current price {}", stop, current)
436            }
437            Self::InvalidLimitPrice { limit, current } => {
438                write!(f, "Invalid limit price {} for current price {}", limit, current)
439            }
440            Self::InvalidBracketOrder(msg) => write!(f, "Invalid bracket order: {}", msg),
441            Self::InvalidPercentage { field, value, min, max } => {
442                write!(f, "Invalid {}: {} (must be between {} and {})", field, value, min, max)
443            }
444        }
445    }
446}
447
448impl std::error::Error for ValidationError {}
449
450/// Represents the outcome of analyzing an order for margin/commission
451#[derive(Debug, Clone, PartialEq)]
452pub struct OrderAnalysis {
453    /// Initial margin requirement returned by TWS.
454    pub initial_margin: Option<f64>,
455    /// Maintenance margin requirement.
456    pub maintenance_margin: Option<f64>,
457    /// Estimated commission for the order.
458    pub commission: Option<f64>,
459    /// Currency for the commission figures.
460    pub commission_currency: String,
461    /// Free-form warnings provided by TWS.
462    pub warning_text: String,
463}
464
465#[cfg(test)]
466mod tests;