ibapi/
orders.rs

1use std::convert::From;
2use std::fmt::Debug;
3
4use serde::{Deserialize, Serialize};
5use time::OffsetDateTime;
6
7use crate::client::{DataStream, ResponseContext, Subscription};
8use crate::contracts::{ComboLeg, ComboLegOpenClose, Contract, DeltaNeutralContract, SecurityType};
9use crate::messages::{IncomingMessages, Notice, OutgoingMessages};
10use crate::messages::{RequestMessage, ResponseMessage};
11use crate::Client;
12use crate::{encode_option_field, ToField};
13use crate::{server_versions, Error};
14
15mod decoders;
16pub(crate) mod encoders;
17#[cfg(test)]
18mod tests;
19
20/// Make sure to test using only your paper trading account when applicable. A good way of finding out if an order type/exchange combination
21/// is possible is by trying to place such order manually using the TWS.
22/// Before contacting our API support team please refer to the available documentation.
23pub mod order_builder;
24
25/// New description
26pub use crate::contracts::TagValue;
27
28const COMPETE_AGAINST_BEST_OFFSET_UP_TO_MID: Option<f64> = Some(f64::INFINITY);
29
30#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
31/// Order describes the order.
32pub struct Order {
33    /// The API client's order id.
34    pub order_id: i32,
35    /// The Solicited field should be used for orders initiated or recommended by the broker or adviser that were approved by the client (by phone, email, chat, verbally, etc.) prior to entry. Please note that orders that the adviser or broker placed without specifically discussing with the client are discretionary orders, not solicited.
36    pub solicited: bool,
37    /// The API client id which placed the order.
38    pub client_id: i32,
39    /// The Host order identifier.
40    pub perm_id: i32,
41    /// Identifies the side.
42    /// Generally available values are BUY and SELL.
43    /// Additionally, SSHORT and SLONG are available in some institutional-accounts only.
44    /// For general account types, a SELL order will be able to enter a short position automatically if the order quantity is larger than your current long position.
45    /// SSHORT is only supported for institutional account configured with Long/Short account segments or clearing with a separate account.
46    /// SLONG is available in specially-configured institutional accounts to indicate that long position not yet delivered is being sold.
47    pub action: Action,
48    /// The number of positions being bought/sold.
49    pub total_quantity: f64,
50    /// The order's type.
51    pub order_type: String,
52    /// The LIMIT price.
53    /// Used for limit, stop-limit and relative orders. In all other cases specify zero. For relative orders with no limit price, also specify zero.
54    pub limit_price: Option<f64>,
55    /// Generic field to contain the stop price for STP LMT orders, trailing amount, etc.
56    pub aux_price: Option<f64>,
57    /// The time in force.
58    /// Valid values are:
59    /// DAY - Valid for the day only.
60    /// GTC - Good until canceled. The order will continue to work within the system and in the marketplace until it executes or is canceled. GTC orders will be automatically be cancelled under the following conditions:
61    /// If a corporate action on a security results in a stock split (forward or reverse), exchange for shares, or distribution of shares. If you do not log into your IB account for 90 days.
62    /// At the end of the calendar quarter following the current quarter. For example, an order placed during the third quarter of 2011 will be canceled at the end of the first quarter of 2012. If the last day is a non-trading day, the cancellation will occur at the close of the final trading day of that quarter. For example, if the last day of the quarter is Sunday, the orders will be cancelled on the preceding Friday.
63    /// Orders that are modified will be assigned a new “Auto Expire” date consistent with the end of the calendar quarter following the current quarter.
64    /// Orders submitted to IB that remain in force for more than one day will not be reduced for dividends. To allow adjustment to your order price on ex-dividend date, consider using a Good-Til-Date/Time (GTD) or Good-after-Time/Date (GAT) order type, or a combination of the two.
65    /// IOC - Immediate or Cancel. Any portion that is not filled as soon as it becomes available in the market is canceled.
66    /// GTD - Good until Date. It will remain working within the system and in the marketplace until it executes or until the close of the market on the date specified
67    /// OPG - Use OPG to send a market-on-open (MOO) or limit-on-open (LOO) order.
68    /// FOK - If the entire Fill-or-Kill order does not execute as soon as it becomes available, the entire order is canceled.
69    /// DTC - Day until Canceled.
70    pub tif: String, // FIXME create enum
71    /// One-Cancels-All group identifier.
72    pub oca_group: String,
73    /// Tells how to handle remaining orders in an OCA group when one order or part of an order executes.
74    /// Valid values are:
75    /// 1 - Cancel all remaining orders with block.
76    /// 2 - Remaining orders are proportionately reduced in size with block.
77    /// 3 - Remaining orders are proportionately reduced in size with no block.
78    /// If you use a value "with block" it gives the order overfill protection. This means that only one order in the group will be routed at a time to remove the possibility of an overfill.
79    pub oca_type: i32,
80    /// The order reference.
81    /// Intended for institutional customers only, although all customers may use it to identify the API client that sent the order when multiple API clients are running.
82    pub order_ref: String,
83    /// Specifies whether the order will be transmitted by TWS. If set to false, the order will be created at TWS but will not be sent.
84    pub transmit: bool,
85    /// The order ID of the parent order, used for bracket and auto trailing stop orders.
86    pub parent_id: i32,
87    /// If set to true, specifies that the order is an ISE Block order.
88    pub block_order: bool,
89    /// If set to true, specifies that the order is a Sweep-to-Fill order.
90    pub sweep_to_fill: bool,
91    /// The publicly disclosed order size, used when placing Iceberg orders.
92    pub display_size: Option<i32>,
93    /// Specifies how Simulated Stop, Stop-Limit and Trailing Stop orders are triggered.
94    /// Valid values are:
95    /// 0 - The default value. The "double bid/ask" function will be used for orders for OTC stocks and US options. All other orders will used the "last" function.
96    /// 1 - use "double bid/ask" function, where stop orders are triggered based on two consecutive bid or ask prices.
97    /// 2 - "last" function, where stop orders are triggered based on the last price.
98    /// 3 - double last function.
99    /// 4 - bid/ask function.
100    /// 7 - last or bid/ask function.
101    /// 8 - mid-point function.    
102    pub trigger_method: i32,
103    /// If set to true, allows orders to also trigger or fill outside of regular trading hours.
104    pub outside_rth: bool,
105    /// If set to true, the order will not be visible when viewing the market depth. This option only applies to orders routed to the NASDAQ exchange.
106    pub hidden: bool,
107    /// Specifies the date and time after which the order will be active.
108    /// Format: yyyymmdd hh:mm:ss {optional Timezone}.
109    pub good_after_time: String,
110    /// The date and time until the order will be active.
111    /// You must enter GTD as the time in force to use this string. The trade's "Good Till Date," format "yyyyMMdd HH:mm:ss (optional time zone)" or UTC "yyyyMMdd-HH:mm:ss".
112    pub good_till_date: String,
113    /// Overrides TWS constraints.
114    /// Precautionary constraints are defined on the TWS Presets page, and help ensure tha tyour price and size order values are reasonable. Orders sent from the API are also validated against these safety constraints, and may be rejected if any constraint is violated. To override validation, set this parameter’s value to True.
115    pub override_percentage_constraints: bool,
116    /// Individual = 'I', Agency = 'A', AgentOtherMember = 'W', IndividualPTIA = 'J', AgencyPTIA = 'U', AgentOtherMemberPTIA = 'M', IndividualPT = 'K', AgencyPT = 'Y', AgentOtherMemberPT = 'N'
117    pub rule_80_a: Option<Rule80A>,
118    /// Indicates whether or not all the order has to be filled on a single execution.
119    pub all_or_none: bool,
120    /// Identifies a minimum quantity order type.
121    pub min_qty: Option<i32>,
122    /// The percent offset amount for relative orders.
123    pub percent_offset: Option<f64>,
124    /// Trail stop price for TRAIL LIMIT orders.
125    pub trail_stop_price: Option<f64>,
126    /// Specifies the trailing amount of a trailing stop order as a percentage.
127    /// Observe the following guidelines when using the trailingPercent field:
128    ///
129    /// This field is mutually exclusive with the existing trailing amount. That is, the API client can send one or the other but not both.
130    /// This field is read AFTER the stop price (barrier price) as follows: deltaNeutralAuxPrice stopPrice, trailingPercent, scale order attributes
131    /// The field will also be sent to the API in the openOrder message if the API client version is >= 56. It is sent after the stopPrice field as follows: stopPrice, trailingPct, basisPoint.    
132    pub trailing_percent: Option<f64>,
133    /// The Financial Advisor group the trade will be allocated to. Use an empty string if not applicable.
134    pub fa_group: String,
135    /// The Financial Advisor allocation profile the trade will be allocated to. Use an empty string if not applicable.
136    pub fa_profile: String,
137    /// The Financial Advisor allocation method the trade will be allocated to. Use an empty string if not applicable.
138    pub fa_method: String,
139    /// The Financial Advisor percentage concerning the trade's allocation. Use an empty string if not applicable.
140    pub fa_percentage: String,
141    /// For institutional customers only. Valid values are O (open) and C (close).
142    /// Available for institutional clients to determine if this order is to open or close a position.
143    /// When Action = "BUY" and OpenClose = "O" this will open a new position.
144    /// When Action = "BUY" and OpenClose = "C" this will close and existing short position.    
145    pub open_close: Option<OrderOpenClose>,
146    /// The order's origin. Same as TWS "Origin" column. Identifies the type of customer from which the order originated.
147    /// Valid values are:
148    /// 0 - Customer
149    /// 1 - Firm.
150    pub origin: i32,
151    /// For institutions only.
152    /// Valid values are:
153    /// 1 - Broker holds shares
154    /// 2 - Shares come from elsewhere.    
155    pub short_sale_slot: i32,
156    /// For institutions only. Indicates the location where the shares to short come from. Used only when short sale slot is set to 2 (which means that the shares to short are held elsewhere and not with IB).
157    pub designated_location: String,
158    /// Only available with IB Execution-Only accounts with applicable securities.
159    /// Mark order as exempt from short sale uptick rule.
160    pub exempt_code: i32,
161    /// The amount off the limit price allowed for discretionary orders.
162    pub discretionary_amt: f64,
163    /// Use to opt out of default SmartRouting for orders routed directly to ASX.
164    /// This attribute defaults to false unless explicitly set to true.
165    /// When set to false, orders routed directly to ASX will NOT use SmartRouting.
166    /// When set to true, orders routed directly to ASX orders WILL use SmartRouting.
167    pub opt_out_smart_routing: bool,
168    /// For BOX orders only.
169    /// Values include:
170    /// 1 - Match
171    /// 2 - Improvement
172    /// 3 - Transparent.
173    pub auction_strategy: Option<i32>, // FIXME enum
174    /// The auction's starting price. For BOX orders only.
175    pub starting_price: Option<f64>,
176    /// The stock's reference price.
177    /// The reference price is used for VOL orders to compute the limit price sent to an exchange (whether or not Continuous Update is selected), and for price range monitoring.    
178    pub stock_ref_price: Option<f64>,
179    /// The stock's Delta. For orders on BOX only.
180    pub delta: Option<f64>,
181    /// The lower value for the acceptable underlying stock price range.
182    /// For price improvement option orders on BOX and VOL orders with dynamic management.    
183    pub stock_range_lower: Option<f64>,
184    /// The upper value for the acceptable underlying stock price range.
185    /// For price improvement option orders on BOX and VOL orders with dynamic management.
186    pub stock_range_upper: Option<f64>,
187    /// The option price in volatility, as calculated by TWS' Option Analytics.
188    /// This value is expressed as a percent and is used to calculate the limit price sent to the exchange.
189    pub volatility: Option<f64>,
190    /// Values include:
191    /// 1 - Daily Volatility
192    /// 2 - Annual Volatility.
193    pub volatility_type: Option<i32>, // FIXM enum
194    /// Specifies whether TWS will automatically update the limit price of the order as the underlying price moves. VOL orders only.
195    pub continuous_update: bool,
196    /// Specifies how you want TWS to calculate the limit price for options, and for stock range price monitoring.
197    /// VOL orders only.
198    /// Valid values include:
199    /// 1 - Average of NBBO
200    /// 2 - NBB or the NBO depending on the action and right.
201    pub reference_price_type: Option<i32>,
202    /// Enter an order type to instruct TWS to submit a delta neutral trade on full or partial execution of the VOL order. VOL orders only. For no hedge delta order to be sent, specify NONE.
203    pub delta_neutral_order_type: String,
204    /// Use this field to enter a value if the value in the deltaNeutralOrderType field is an order type that requires an Aux price, such as a REL order. VOL orders only.
205    pub delta_neutral_aux_price: Option<f64>,
206    /// The unique contract identifier specifying the security in Delta Neutral order.
207    pub delta_neutral_con_id: i32,
208    /// Indicates the firm which will settle the Delta Neutral trade. Institutions only.
209    pub delta_neutral_settling_firm: String,
210    /// Specifies the beneficiary of the Delta Neutral order.
211    pub delta_neutral_clearing_account: String,
212    /// Specifies where the clients want their shares to be cleared at. Must be specified by execution-only clients.
213    /// Valid values are:
214    /// IB, Away, and PTA (post trade allocation).
215    pub delta_neutral_clearing_intent: String,
216    /// Specifies whether the order is an Open or a Close order and is used when the hedge involves a CFD and and the order is clearing away.
217    pub delta_neutral_open_close: String,
218    /// Used when the hedge involves a stock and indicates whether or not it is sold short.
219    pub delta_neutral_short_sale: bool,
220    /// Indicates a short sale Delta Neutral order. Has a value of 1 (the clearing broker holds shares) or 2 (delivered from a third party). If you use 2, then you must specify a deltaNeutralDesignatedLocation.
221    pub delta_neutral_short_sale_slot: i32,
222    /// Identifies third party order origin. Used only when deltaNeutralShortSaleSlot = 2.
223    pub delta_neutral_designated_location: String,
224    /// Specifies Basis Points for EFP order. The values increment in 0.01% = 1 basis point. For EFP orders only.
225    pub basis_points: Option<f64>,
226    /// Specifies the increment of the Basis Points. For EFP orders only.
227    pub basis_points_type: Option<i32>,
228    /// Defines the size of the first, or initial, order component. For Scale orders only.
229    pub scale_init_level_size: Option<i32>,
230    /// Defines the order size of the subsequent scale order components. For Scale orders only. Used in conjunction with scaleInitLevelSize().
231    pub scale_subs_level_size: Option<i32>,
232    /// Defines the price increment between scale components. For Scale orders only. This value is compulsory.
233    pub scale_price_increment: Option<f64>,
234    /// Modifies the value of the Scale order. For extended Scale orders.
235    pub scale_price_adjust_value: Option<f64>,
236    /// Specifies the interval when the price is adjusted. For extended Scale orders.
237    pub scale_price_adjust_interval: Option<i32>,
238    /// Specifies the offset when to adjust profit. For extended scale orders.
239    pub scale_profit_offset: Option<f64>,
240    /// Restarts the Scale series if the order is cancelled. For extended scale orders.
241    pub scale_auto_reset: bool,
242    /// The initial position of the Scale order. For extended scale orders.
243    pub scale_init_position: Option<i32>,
244    /// Specifies the initial quantity to be filled. For extended scale orders.
245    pub scale_init_fill_qty: Option<i32>,
246    /// Defines the random percent by which to adjust the position. For extended scale orders.
247    pub scale_random_percent: bool,
248    /// For hedge orders.
249    /// Possible values include:
250    /// D - Delta
251    /// B - Beta
252    /// F - FX
253    /// P - Pair
254    pub hedge_type: String,
255    /// For hedge orders.
256    /// Beta = x for Beta hedge orders, ratio = y for Pair hedge order
257    pub hedge_param: String,
258    /// The account the trade will be allocated to.    
259    pub account: String,
260    /// Indicates the firm which will settle the trade. Institutions only.
261    pub settling_firm: String,
262    /// Specifies the true beneficiary of the order.
263    /// For IBExecution customers. This value is required for FUT/FOP orders for reporting to the exchange.
264    pub clearing_account: String,
265    /// For execution-only clients to know where do they want their shares to be cleared at.
266    /// Valid values are:
267    /// IB, Away, and PTA (post trade allocation).
268    pub clearing_intent: String,
269    /// The algorithm strategy.
270    /// As of API version 9.6, the following algorithms are supported:
271    /// ArrivalPx - Arrival Price
272    /// DarkIce - Dark Ice
273    /// PctVol - Percentage of Volume
274    /// Twap - TWAP (Time Weighted Average Price)
275    /// Vwap - VWAP (Volume Weighted Average Price)
276    /// For more information about IB's API algorithms, refer to [https://www.interactivebrokers.com/en/software/api/apiguide/tables/ibalgo_parameters.htm](https://www.interactivebrokers.com/en/software/api/apiguide/tables/ibalgo_parameters.htm)
277    pub algo_strategy: String,
278    /// The list of parameters for the IB algorithm.
279    /// For more information about IB's API algorithms, refer to [https://www.interactivebrokers.com/en/software/api/apiguide/tables/ibalgo_parameters.htm](https://www.interactivebrokers.com/en/software/api/apiguide/tables/ibalgo_parameters.htm)
280    pub algo_params: Vec<TagValue>,
281    /// Allows to retrieve the commissions and margin information.
282    /// When placing an order with this attribute set to true, the order will not be placed as such. Instead it will used to request the commissions and margin information that would result from this order.
283    pub what_if: bool,
284    /// Identifies orders generated by algorithmic trading.
285    pub algo_id: String,
286    /// Orders routed to IBDARK are tagged as “post only” and are held in IB's order book, where incoming SmartRouted orders from other IB customers are eligible to trade against them.
287    /// For IBDARK orders only.
288    pub not_held: bool,
289    /// Advanced parameters for Smart combo routing.
290    /// These features are for both guaranteed and non-guaranteed combination orders routed to Smart, and are available based on combo type and order type. SmartComboRoutingParams is similar to AlgoParams in that it makes use of tag/value pairs to add parameters to combo orders.
291    /// Make sure that you fully understand how Advanced Combo Routing works in TWS itself first: <https://guides.interactivebrokers.com/tws/twsguide.htm#usersguidebook/specializedorderentry/advanced_combo_routing.htm>
292    /// The parameters cover the following capabilities:
293    ///
294    /// * Non-Guaranteed - Determine if the combo order is Guaranteed or Non-Guaranteed.
295    ///   <br/>Tag = NonGuaranteed
296    ///   <br/>Value = 0: The order is guaranteed
297    ///   <br/>Value = 1: The order is non-guaranteed
298    ///
299    /// * Select Leg to Fill First - User can specify which leg to be executed first.
300    ///   <br/>Tag = LeginPrio
301    ///   <br/>Value = -1: No priority is assigned to either combo leg
302    ///   <br/>Value = 0: Priority is assigned to the first leg being added to the comboLeg
303    ///   <br/>Value = 1: Priority is assigned to the second leg being added to the comboLeg
304    ///   <br/>Note: The LeginPrio parameter can only be applied to two-legged combo.
305    ///
306    /// * Maximum Leg-In Combo Size - Specify the maximum allowed leg-in size per segment
307    ///   <br/>Tag = MaxSegSize
308    ///   <br/>Value = Unit of combo size
309    ///
310    /// * Do Not Start Next Leg-In if Previous Leg-In Did Not Finish - Specify whether or not the system should attempt to fill the next segment before the current segment fills.
311    ///   <br/>Tag = DontLeginNext
312    ///   <br/>Value = 0: Start next leg-in even if previous leg-in did not finish
313    ///   <br/>Value = 1: Do not start next leg-in if previous leg-in did not finish
314    ///
315    /// * Price Condition - Combo order will be rejected or cancelled if the leg market price is outside of the specified price range [CondPriceMin, CondPriceMax]
316    ///   <br/>Tag = PriceCondConid: The ContractID of the combo leg to specify price condition on
317    ///   <br/>Value = The ContractID
318    ///   <br/>Tag = CondPriceMin: The lower price range of the price condition
319    ///   <br/>Value = The lower price
320    ///   <br/>Tag = CondPriceMax: The upper price range of the price condition
321    ///   <br/>Value = The upper price    
322    pub smart_combo_routing_params: Vec<TagValue>,
323    /// List of Per-leg price following the same sequence combo legs are added. The combo price must be left unspecified when using per-leg prices.
324    pub order_combo_legs: Vec<OrderComboLeg>,
325    /// For internal use only. Use the default value XYZ.
326    pub order_misc_options: Vec<TagValue>,
327    /// Defines the start time of GTC orders.
328    pub active_start_time: String,
329    /// Defines the stop time of GTC orders.
330    pub active_stop_time: String,
331    /// The list of scale orders. Used for scale orders.
332    pub scale_table: String,
333    /// Is used to place an order to a model. For example, "Technology" model can be used for tech stocks first created in TWS.
334    pub model_code: String,
335    /// This is a regulatory attribute that applies to all US Commodity (Futures) Exchanges, provided to allow client to comply with CFTC Tag 50 Rules.
336    pub ext_operator: String,
337    /// The native cash quantity.
338    pub cash_qty: Option<f64>,
339    /// Identifies a person as the responsible party for investment decisions within the firm. Orders covered by MiFID 2 (Markets in Financial Instruments Directive 2) must include either Mifid2DecisionMaker or Mifid2DecisionAlgo field (but not both). Requires TWS 969+.
340    pub mifid2_decision_maker: String,
341    /// Identifies the algorithm responsible for investment decisions within the firm. Orders covered under MiFID 2 must include either Mifid2DecisionMaker or Mifid2DecisionAlgo, but cannot have both. Requires TWS 969+.
342    pub mifid2_decision_algo: String,
343    /// For MiFID 2 reporting; identifies a person as the responsible party for the execution of a transaction within the firm. Requires TWS 969+.
344    pub mifid2_execution_trader: String,
345    /// For MiFID 2 reporting; identifies the algorithm responsible for the execution of a transaction within the firm. Requires TWS 969+.
346    pub mifid2_execution_algo: String,
347    /// Don't use auto price for hedge.
348    pub dont_use_auto_price_for_hedge: bool,
349    /// Specifies the date to auto cancel the order.
350    pub auto_cancel_date: String, // TODO date object
351    /// Specifies the initial order quantity to be filled.
352    pub filled_quantity: f64,
353    /// Identifies the reference future conId.
354    pub ref_futures_con_id: Option<i32>,
355    /// Cancels the parent order if child order was cancelled.
356    pub auto_cancel_parent: bool,
357    /// Identifies the Shareholder.
358    pub shareholder: String,
359    /// Used to specify "imbalance only open orders" or "imbalance only closing orders".
360    pub imbalance_only: bool,
361    /// Routes market order to Best Bid Offer.
362    pub route_marketable_to_bbo: bool,
363    /// Parent order Id.
364    pub parent_perm_id: Option<i64>,
365    /// Accepts a list with parameters obtained from advancedOrderRejectJson.
366    pub advanced_error_override: String,
367    /// Used by brokers and advisors when manually entering, modifying or cancelling orders at the direction of a client. Only used when allocating orders to specific groups or accounts. Excluding "All" group.
368    pub manual_order_time: String,
369    /// Defines the minimum trade quantity to fill. For IBKRATS orders.
370    pub min_trade_qty: Option<i32>,
371    /// Defines the minimum size to compete. For IBKRATS orders.
372    pub min_compete_size: Option<i32>,
373    /// Specifies the offset off the midpoint that will be applied to the order. For IBKRATS orders.
374    pub compete_against_best_offset: Option<f64>,
375    /// his offset is applied when the spread is an even number of cents wide. This offset must be in whole-penny increments or zero. For IBKRATS orders.
376    pub mid_offset_at_whole: Option<f64>,
377    /// This offset is applied when the spread is an odd number of cents wide. This offset must be in half-penny increments. For IBKRATS orders.
378    pub mid_offset_at_half: Option<f64>,
379    /// Randomizes the order's size. Only for Volatility and Pegged to Volatility orders.
380    pub randomize_size: bool,
381    /// Randomizes the order's price. Only for Volatility and Pegged to Volatility orders.
382    pub randomize_price: bool,
383    /// Pegged-to-benchmark orders: this attribute will contain the conId of the contract against which the order will be pegged.
384    pub reference_contract_id: i32,
385    /// Pegged-to-benchmark orders: indicates whether the order's pegged price should increase or decreases.
386    pub is_pegged_change_amount_decrease: bool,
387    /// Pegged-to-benchmark orders: amount by which the order's pegged price should move.
388    pub pegged_change_amount: Option<f64>,
389    /// Pegged-to-benchmark orders: the amount the reference contract needs to move to adjust the pegged order.
390    pub reference_change_amount: Option<f64>,
391    /// Pegged-to-benchmark orders: the exchange against which we want to observe the reference contract.
392    pub reference_exchange: String,
393    /// Adjusted Stop orders: the parent order will be adjusted to the given type when the adjusted trigger price is penetrated.
394    pub adjusted_order_type: String,
395    /// Adjusted Stop orders: specifies the trigger price to execute.
396    pub trigger_price: Option<f64>,
397    /// Adjusted Stop orders: specifies the price offset for the stop to move in increments.
398    pub limit_price_offset: Option<f64>,
399    /// Adjusted Stop orders: specifies the stop price of the adjusted (STP) parent.
400    pub adjusted_stop_price: Option<f64>,
401    /// Adjusted Stop orders: specifies the stop limit price of the adjusted (STPL LMT) parent.
402    pub adjusted_stop_limit_price: Option<f64>,
403    /// Adjusted Stop orders: specifies the trailing amount of the adjusted (TRAIL) parent.
404    pub adjusted_trailing_amount: Option<f64>,
405    /// Adjusted Stop orders: specifies where the trailing unit is an amount (set to 0) or a percentage (set to 1)
406    pub adjustable_trailing_unit: i32,
407    /// Conditions determining when the order will be activated or canceled.
408    pub conditions: Vec<OrderCondition>,
409    /// Indicates whether or not conditions will also be valid outside Regular Trading Hours.
410    pub conditions_ignore_rth: bool,
411    /// Conditions can determine if an order should become active or canceled.
412    pub conditions_cancel_order: bool,
413    /// Define the Soft Dollar Tier used for the order. Only provided for registered professional advisors and hedge and mutual funds.
414    pub soft_dollar_tier: SoftDollarTier,
415    /// Set to true to create tickets from API orders when TWS is used as an OMS.
416    pub is_oms_container: bool,
417    /// Set to true to convert order of type 'Primary Peg' to 'D-Peg'.
418    pub discretionary_up_to_limit_price: bool,
419    /// Specifies wether to use Price Management Algo. CTCI users only.
420    pub use_price_mgmt_algo: bool,
421    /// Specifies the duration of the order. Format: yyyymmdd hh:mm:ss TZ. For GTD orders.
422    pub duration: Option<i32>, // TODO date object?
423    /// Value must be positive, and it is number of seconds that SMART order would be parked for at IBKRATS before being routed to exchange.
424    pub post_to_ats: Option<i32>,
425}
426
427impl Default for Order {
428    fn default() -> Self {
429        Self {
430            order_id: 0,
431            solicited: false,
432            client_id: 0,
433            perm_id: 0,
434            action: Action::Buy,
435            total_quantity: 0.0,
436            order_type: "".to_owned(),
437            limit_price: None,
438            aux_price: None,
439            tif: "".to_owned(),
440            oca_group: "".to_owned(),
441            oca_type: 0,
442            order_ref: "".to_owned(),
443            transmit: true,
444            parent_id: 0,
445            block_order: false,
446            sweep_to_fill: false,
447            display_size: Some(0), // TODO - default to None?
448            trigger_method: 0,
449            outside_rth: false,
450            hidden: false,
451            good_after_time: "".to_owned(),
452            good_till_date: "".to_owned(),
453            override_percentage_constraints: false,
454            rule_80_a: None,
455            all_or_none: false,
456            min_qty: None,
457            percent_offset: None,
458            trail_stop_price: None,
459            trailing_percent: None,
460            fa_group: "".to_owned(),
461            fa_profile: "".to_owned(),
462            fa_method: "".to_owned(),
463            fa_percentage: "".to_owned(),
464            open_close: None,
465            origin: 0,
466            short_sale_slot: 0,
467            designated_location: "".to_owned(),
468            exempt_code: -1,
469            discretionary_amt: 0.0,
470            opt_out_smart_routing: false,
471            auction_strategy: Some(0), // TODO - use enum
472            starting_price: None,
473            stock_ref_price: None,
474            delta: None,
475            stock_range_lower: None,
476            stock_range_upper: None,
477            volatility: None,
478            volatility_type: None,
479            continuous_update: false,
480            reference_price_type: None,
481            delta_neutral_order_type: "".to_owned(),
482            delta_neutral_aux_price: None,
483            delta_neutral_con_id: 0,
484            delta_neutral_settling_firm: "".to_owned(),
485            delta_neutral_clearing_account: "".to_owned(),
486            delta_neutral_clearing_intent: "".to_owned(),
487            delta_neutral_open_close: "".to_owned(),
488            delta_neutral_short_sale: false,
489            delta_neutral_short_sale_slot: 0,
490            delta_neutral_designated_location: "".to_owned(),
491            basis_points: Some(0.0),
492            basis_points_type: Some(0),
493            scale_init_level_size: None,
494            scale_subs_level_size: None,
495            scale_price_increment: None,
496            scale_price_adjust_value: None,
497            scale_price_adjust_interval: None,
498            scale_profit_offset: None,
499            scale_auto_reset: false,
500            scale_init_position: None,
501            scale_init_fill_qty: None,
502            scale_random_percent: false,
503            hedge_type: "".to_owned(),
504            hedge_param: "".to_owned(),
505            account: "".to_owned(),
506            settling_firm: "".to_owned(),
507            clearing_account: "".to_owned(),
508            clearing_intent: "".to_owned(),
509            algo_strategy: "".to_owned(),
510            algo_params: vec![],
511            what_if: false,
512            algo_id: "".to_owned(),
513            not_held: false,
514            smart_combo_routing_params: vec![],
515            order_combo_legs: vec![],
516            order_misc_options: vec![],
517            active_start_time: "".to_owned(),
518            active_stop_time: "".to_owned(),
519            scale_table: "".to_owned(),
520            model_code: "".to_owned(),
521            ext_operator: "".to_owned(),
522            cash_qty: None,
523            mifid2_decision_maker: "".to_owned(),
524            mifid2_decision_algo: "".to_owned(),
525            mifid2_execution_trader: "".to_owned(),
526            mifid2_execution_algo: "".to_owned(),
527            dont_use_auto_price_for_hedge: false,
528            auto_cancel_date: "".to_owned(),
529            filled_quantity: 0.0,
530            ref_futures_con_id: Some(0),
531            auto_cancel_parent: false,
532            shareholder: "".to_owned(),
533            imbalance_only: false,
534            route_marketable_to_bbo: false,
535            parent_perm_id: None,
536            advanced_error_override: "".to_owned(),
537            manual_order_time: "".to_owned(),
538            min_trade_qty: None,
539            min_compete_size: None,
540            compete_against_best_offset: None,
541            mid_offset_at_whole: None,
542            mid_offset_at_half: None,
543            randomize_size: false,
544            randomize_price: false,
545            reference_contract_id: 0,
546            is_pegged_change_amount_decrease: false,
547            pegged_change_amount: Some(0.0),
548            reference_change_amount: Some(0.0),
549            reference_exchange: "".to_owned(),
550            adjusted_order_type: "".to_owned(),
551            trigger_price: None,
552            limit_price_offset: None,
553            adjusted_stop_price: None,
554            adjusted_stop_limit_price: None,
555            adjusted_trailing_amount: None,
556            adjustable_trailing_unit: 0,
557            conditions: vec![],
558            conditions_ignore_rth: false,
559            conditions_cancel_order: false,
560            soft_dollar_tier: SoftDollarTier::default(),
561            is_oms_container: false,
562            discretionary_up_to_limit_price: false,
563            use_price_mgmt_algo: false,
564            duration: None,
565            post_to_ats: None,
566        }
567    }
568}
569
570impl Order {
571    pub fn is_delta_neutral(&self) -> bool {
572        !self.delta_neutral_order_type.is_empty()
573    }
574
575    pub fn is_scale_order(&self) -> bool {
576        match self.scale_price_increment {
577            Some(price_increment) => price_increment > 0.0,
578            _ => false,
579        }
580    }
581}
582
583/// Identifies the side.
584/// Generally available values are BUY and SELL.
585/// Additionally, SSHORT and SLONG are available in some institutional-accounts only.
586/// For general account types, a SELL order will be able to enter a short position automatically if the order quantity is larger than your current long position.
587/// SSHORT is only supported for institutional account configured with Long/Short account segments or clearing with a separate account.
588/// SLONG is available in specially-configured institutional accounts to indicate that long position not yet delivered is being sold.
589#[derive(Clone, Debug, Default, PartialEq, Eq, Copy, Serialize, Deserialize)]
590pub enum Action {
591    #[default]
592    Buy,
593    Sell,
594    /// SSHORT is only supported for institutional account configured with Long/Short account segments or clearing with a separate account.
595    SellShort,
596    /// SLONG is available in specially-configured institutional accounts to indicate that long position not yet delivered is being sold.
597    SellLong,
598}
599
600impl ToField for Action {
601    fn to_field(&self) -> String {
602        self.to_string()
603    }
604}
605
606impl std::fmt::Display for Action {
607    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
608        let text = match self {
609            Action::Buy => "BUY",
610            Action::Sell => "SELL",
611            Action::SellShort => "SSHORT",
612            Action::SellLong => "SLONG",
613        };
614
615        write!(f, "{text}")
616    }
617}
618
619impl Action {
620    pub fn reverse(self) -> Action {
621        match self {
622            Action::Buy => Action::Sell,
623            Action::Sell => Action::Buy,
624            Action::SellShort => Action::SellLong,
625            Action::SellLong => Action::SellShort,
626        }
627    }
628
629    pub fn from(name: &str) -> Self {
630        match name {
631            "BUY" => Self::Buy,
632            "SELL" => Self::Sell,
633            "SSHORT" => Self::SellShort,
634            "SLONG" => Self::SellLong,
635            &_ => todo!(),
636        }
637    }
638}
639
640#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
641pub enum Rule80A {
642    Individual,
643    Agency,
644    AgentOtherMember,
645    IndividualPTIA,
646    AgencyPTIA,
647    AgentOtherMemberPTIA,
648    IndividualPT,
649    AgencyPT,
650    AgentOtherMemberPT,
651}
652
653impl ToField for Rule80A {
654    fn to_field(&self) -> String {
655        self.to_string()
656    }
657}
658
659impl ToField for Option<Rule80A> {
660    fn to_field(&self) -> String {
661        encode_option_field(self)
662    }
663}
664
665impl std::fmt::Display for Rule80A {
666    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
667        let text = match self {
668            Rule80A::Individual => "I",
669            Rule80A::Agency => "A",
670            Rule80A::AgentOtherMember => "W",
671            Rule80A::IndividualPTIA => "J",
672            Rule80A::AgencyPTIA => "U",
673            Rule80A::AgentOtherMemberPTIA => "M",
674            Rule80A::IndividualPT => "K",
675            Rule80A::AgencyPT => "Y",
676            Rule80A::AgentOtherMemberPT => "N",
677        };
678
679        write!(f, "{text}")
680    }
681}
682
683impl Rule80A {
684    pub fn from(source: &str) -> Option<Self> {
685        match source {
686            "I" => Some(Rule80A::Individual),
687            "A" => Some(Rule80A::Agency),
688            "W" => Some(Rule80A::AgentOtherMember),
689            "J" => Some(Rule80A::IndividualPTIA),
690            "U" => Some(Rule80A::AgencyPTIA),
691            "M" => Some(Rule80A::AgentOtherMemberPTIA),
692            "K" => Some(Rule80A::IndividualPT),
693            "Y" => Some(Rule80A::AgencyPT),
694            "N" => Some(Rule80A::AgentOtherMemberPT),
695            _ => None,
696        }
697    }
698}
699
700pub enum AuctionStrategy {
701    Match,
702    Improvement,
703    Transparent,
704}
705
706#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)]
707pub struct OrderComboLeg {
708    price: Option<f64>,
709}
710
711#[derive(Copy, Clone, Debug, PartialEq, Serialize, Deserialize)]
712pub enum OrderCondition {
713    Price = 1,
714    Time = 3,
715    Margin = 4,
716    Execution = 5,
717    Volume = 6,
718    PercentChange = 7,
719}
720
721impl ToField for OrderCondition {
722    fn to_field(&self) -> String {
723        (*self as u8).to_string()
724    }
725}
726
727impl ToField for Option<OrderCondition> {
728    fn to_field(&self) -> String {
729        encode_option_field(self)
730    }
731}
732
733impl From<i32> for OrderCondition {
734    fn from(val: i32) -> Self {
735        match val {
736            1 => OrderCondition::Price,
737            3 => OrderCondition::Time,
738            4 => OrderCondition::Volume,
739            5 => OrderCondition::Execution,
740            6 => OrderCondition::Volume,
741            7 => OrderCondition::PercentChange,
742            _ => panic!("OrderCondition({val}) is unsupported"),
743        }
744    }
745}
746
747/// Stores Soft Dollar Tier information.
748#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)]
749pub struct SoftDollarTier {
750    pub name: String,
751    pub value: String,
752    pub display_name: String,
753}
754
755#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)]
756pub struct OrderData {
757    /// The order's unique id
758    pub order_id: i32,
759    /// The order's Contract.
760    pub contract: Contract,
761    /// The currently active order
762    pub order: Order,
763    /// The order's OrderState
764    pub order_state: OrderState,
765}
766
767/// Provides an active order's current state.
768#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)]
769pub struct OrderState {
770    /// The order's current status
771    pub status: String,
772    /// The account's current initial margin.
773    pub initial_margin_before: Option<f64>,
774    /// The account's current maintenance margin
775    pub maintenance_margin_before: Option<f64>,
776    /// The account's current equity with loan
777    pub equity_with_loan_before: Option<f64>,
778    /// The change of the account's initial margin.
779    pub initial_margin_change: Option<f64>,
780    /// The change of the account's maintenance margin
781    pub maintenance_margin_change: Option<f64>,
782    /// The change of the account's equity with loan
783    pub equity_with_loan_change: Option<f64>,
784    /// The order's impact on the account's initial margin.
785    pub initial_margin_after: Option<f64>,
786    /// The order's impact on the account's maintenance margin
787    pub maintenance_margin_after: Option<f64>,
788    /// Shows the impact the order would have on the account's equity with loan
789    pub equity_with_loan_after: Option<f64>,
790    /// The order's generated commission.
791    pub commission: Option<f64>,
792    // The execution's minimum commission.
793    pub minimum_commission: Option<f64>,
794    /// The executions maximum commission.
795    pub maximum_commission: Option<f64>,
796    /// The generated commission currency
797    pub commission_currency: String,
798    /// If the order is warranted, a descriptive message will be provided.
799    pub warning_text: String,
800    pub completed_time: String,
801    pub completed_status: String,
802}
803
804/// For institutional customers only. Valid values are O (open) and C (close).
805/// Available for institutional clients to determine if this order is to open or close a position.
806/// When Action = "BUY" and OpenClose = "O" this will open a new position.
807/// When Action = "BUY" and OpenClose = "C" this will close and existing short position.
808#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
809pub enum OrderOpenClose {
810    Open,
811    Close,
812}
813
814impl ToField for OrderOpenClose {
815    fn to_field(&self) -> String {
816        self.to_string()
817    }
818}
819
820impl ToField for Option<OrderOpenClose> {
821    fn to_field(&self) -> String {
822        encode_option_field(self)
823    }
824}
825
826impl std::fmt::Display for OrderOpenClose {
827    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
828        let text = match self {
829            OrderOpenClose::Open => "O",
830            OrderOpenClose::Close => "C",
831        };
832
833        write!(f, "{text}")
834    }
835}
836
837impl OrderOpenClose {
838    pub fn from(source: &str) -> Option<Self> {
839        match source {
840            "O" => Some(OrderOpenClose::Open),
841            "C" => Some(OrderOpenClose::Close),
842            _ => None,
843        }
844    }
845}
846
847/// Represents the commission generated by an execution.
848#[derive(Clone, Debug, Default)]
849pub struct CommissionReport {
850    /// the execution's id this commission belongs to.
851    pub execution_id: String,
852    /// the commissions cost.
853    pub commission: f64,
854    /// the reporting currency.
855    pub currency: String,
856    /// the realized profit and loss
857    pub realized_pnl: Option<f64>,
858    /// The income return.
859    pub yields: Option<f64>,
860    /// date expressed in yyyymmdd format.
861    pub yield_redemption_date: String,
862}
863
864#[derive(Clone, Debug, Default, PartialEq)]
865pub enum Liquidity {
866    #[default]
867    None = 0,
868    AddedLiquidity = 1,
869    RemovedLiquidity = 2,
870    LiquidityRoutedOut = 3,
871}
872
873impl From<i32> for Liquidity {
874    fn from(val: i32) -> Self {
875        match val {
876            1 => Liquidity::AddedLiquidity,
877            2 => Liquidity::RemovedLiquidity,
878            3 => Liquidity::LiquidityRoutedOut,
879            _ => Liquidity::None,
880        }
881    }
882}
883
884/// Describes an order's execution.
885#[derive(Clone, Debug, Default)]
886pub struct Execution {
887    /// The API client's order Id. May not be unique to an account.
888    pub order_id: i32,
889    /// The API client identifier which placed the order which originated this execution.
890    pub client_id: i32,
891    /// The execution's identifier. Each partial fill has a separate ExecId.
892    /// A correction is indicated by an ExecId which differs from a previous ExecId in only the digits after the final period,
893    /// e.g. an ExecId ending in ".02" would be a correction of a previous execution with an ExecId ending in ".01"
894    pub execution_id: String,
895    /// The execution's server time.
896    pub time: String,
897    /// The account to which the order was allocated.
898    pub account_number: String,
899    /// The exchange where the execution took place.
900    pub exchange: String,
901    /// Specifies if the transaction was buy or sale
902    /// BOT for bought, SLD for sold
903    pub side: String,
904    /// The number of shares filled.
905    pub shares: f64,
906    /// The order's execution price excluding commissions.
907    pub price: f64,
908    /// The TWS order identifier. The PermId can be 0 for trades originating outside IB.
909    pub perm_id: i32,
910    /// Identifies whether an execution occurred because of an IB-initiated liquidation.
911    pub liquidation: i32,
912    /// Cumulative quantity.
913    // Used in regular trades, combo trades and legs of the combo.
914    pub cumulative_quantity: f64,
915    /// Average price.
916    /// Used in regular trades, combo trades and legs of the combo. Does not include commissions.
917    pub average_price: f64,
918    /// The OrderRef is a user-customizable string that can be set from the API or TWS and will be associated with an order for its lifetime.
919    pub order_reference: String,
920    /// The Economic Value Rule name and the respective optional argument.
921    /// The two values should be separated by a colon. For example, aussieBond:YearsToExpiration=3. When the optional argument is not present, the first value will be followed by a colon.
922    pub ev_rule: String,
923    /// Tells you approximately how much the market value of a contract would change if the price were to change by 1.
924    /// It cannot be used to get market value by multiplying the price by the approximate multiplier.
925    pub ev_multiplier: Option<f64>,
926    /// model code
927    pub model_code: String,
928    // The liquidity type of the execution. Requires TWS 968+ and API v973.05+. Python API specifically requires API v973.06+.
929    pub last_liquidity: Liquidity,
930}
931
932#[derive(Clone, Debug, Default)]
933pub struct ExecutionData {
934    pub request_id: i32,
935    pub contract: Contract,
936    pub execution: Execution,
937}
938
939#[derive(Clone, Debug)]
940#[allow(clippy::large_enum_variant)]
941pub enum PlaceOrder {
942    OrderStatus(OrderStatus),
943    OpenOrder(OrderData),
944    ExecutionData(ExecutionData),
945    CommissionReport(CommissionReport),
946    Message(Notice),
947}
948
949/// Contains all relevant information on the current status of the order execution-wise (i.e. amount filled and pending, filling price, etc.).
950#[derive(Clone, Debug, Default, Serialize, Deserialize, PartialEq)]
951pub struct OrderStatus {
952    /// The order's client id.
953    pub order_id: i32,
954    /// The current status of the order. Possible values:
955    ///     ApiPending - indicates order has not yet been sent to IB server, for instance if there is a delay in receiving the security definition. Uncommonly received.
956    ///     PendingSubmit - indicates that you have transmitted the order, but have not yet received confirmation that it has been accepted by the order destination.
957    ///     PendingCancel - indicates that you have sent a request to cancel the order but have not yet received cancel confirmation from the order destination. At this point, your order is not confirmed canceled. It is not guaranteed that the cancellation will be successful.
958    ///     PreSubmitted - indicates that a simulated order type has been accepted by the IB system and that this order has yet to be elected. The order is held in the IB system until the election criteria are met. At that time the order is transmitted to the order destination as specified .
959    ///     Submitted - indicates that your order has been accepted by the system.
960    ///     ApiCancelled - after an order has been submitted and before it has been acknowledged, an API client client can request its cancelation, producing this state.
961    ///     Cancelled - indicates that the balance of your order has been confirmed canceled by the IB system. This could occur unexpectedly when IB or the destination has rejected your order.
962    ///     Filled - indicates that the order has been completely filled. Market orders executions will not always trigger a Filled status.
963    ///     Inactive - indicates that the order was received by the system but is no longer active because it was rejected or canceled.    
964    pub status: String,
965    /// Number of filled positions.
966    pub filled: f64,
967    /// The remnant positions.
968    pub remaining: f64,
969    /// Average filling price.
970    pub average_fill_price: f64,
971    /// The order's permId used by the TWS to identify orders.
972    pub perm_id: i32,
973    /// Parent's id. Used for bracket and auto trailing stop orders.
974    pub parent_id: i32,
975    /// Price at which the last positions were filled.
976    pub last_fill_price: f64,
977    /// API client which submitted the order.
978    pub client_id: i32,
979    /// This field is used to identify an order held when TWS is trying to locate shares for a short sell. The value used to indicate this is 'locate'.
980    pub why_held: String,
981    /// If an order has been capped, this indicates the current capped price. Requires TWS 967+ and API v973.04+. Python API specifically requires API v973.06+.
982    pub market_cap_price: f64,
983}
984
985// Submits an Order.
986// After the order is submitted correctly, events will be returned concerning the order's activity.
987// https://interactivebrokers.github.io/tws-api/order_submission.html
988pub(crate) fn place_order<'a>(client: &'a Client, order_id: i32, contract: &Contract, order: &Order) -> Result<Subscription<'a, PlaceOrder>, Error> {
989    verify_order(client, order, order_id)?;
990    verify_order_contract(client, contract, order_id)?;
991
992    let request = encoders::encode_place_order(client.server_version(), order_id, contract, order)?;
993    let subscription = client.send_order(order_id, request)?;
994
995    Ok(Subscription::new(client, subscription, ResponseContext::default()))
996}
997
998impl DataStream<PlaceOrder> for PlaceOrder {
999    fn decode(client: &Client, message: &mut ResponseMessage) -> Result<PlaceOrder, Error> {
1000        match message.message_type() {
1001            IncomingMessages::OpenOrder => Ok(PlaceOrder::OpenOrder(decoders::decode_open_order(
1002                client.server_version,
1003                message.clone(),
1004            )?)),
1005            IncomingMessages::OrderStatus => Ok(PlaceOrder::OrderStatus(decoders::decode_order_status(client.server_version, message)?)),
1006            IncomingMessages::ExecutionData => Ok(PlaceOrder::ExecutionData(decoders::decode_execution_data(
1007                client.server_version,
1008                message,
1009            )?)),
1010            IncomingMessages::CommissionsReport => Ok(PlaceOrder::CommissionReport(decoders::decode_commission_report(
1011                client.server_version,
1012                message,
1013            )?)),
1014            IncomingMessages::Error => Ok(PlaceOrder::Message(Notice::from(message))),
1015            _ => Err(Error::UnexpectedResponse(message.clone())),
1016        }
1017    }
1018}
1019
1020// Verifies that Order is properly formed.
1021fn verify_order(client: &Client, order: &Order, _order_id: i32) -> Result<(), Error> {
1022    let is_bag_order: bool = false; // StringsAreEqual(Constants.BagSecType, contract.SecType)
1023
1024    if order.scale_init_level_size.is_some() || order.scale_price_increment.is_some() {
1025        client.check_server_version(server_versions::SCALE_ORDERS, "It does not support Scale orders.")?
1026    }
1027
1028    if order.what_if {
1029        client.check_server_version(server_versions::WHAT_IF_ORDERS, "It does not support what-if orders.")?
1030    }
1031
1032    if order.scale_subs_level_size.is_some() {
1033        client.check_server_version(
1034            server_versions::SCALE_ORDERS2,
1035            "It does not support Subsequent Level Size for Scale orders.",
1036        )?
1037    }
1038
1039    if !order.algo_strategy.is_empty() {
1040        client.check_server_version(server_versions::ALGO_ORDERS, "It does not support algo orders.")?
1041    }
1042
1043    if order.not_held {
1044        client.check_server_version(server_versions::NOT_HELD, "It does not support not_held parameter.")?
1045    }
1046
1047    if order.exempt_code != -1 {
1048        client.check_server_version(server_versions::SSHORTX, "It does not support exempt_code parameter.")?
1049    }
1050
1051    if !order.hedge_type.is_empty() {
1052        client.check_server_version(server_versions::HEDGE_ORDERS, "It does not support hedge orders.")?
1053    }
1054
1055    if order.opt_out_smart_routing {
1056        client.check_server_version(
1057            server_versions::OPT_OUT_SMART_ROUTING,
1058            "It does not support opt_out_smart_routing parameter.",
1059        )?
1060    }
1061
1062    if order.delta_neutral_con_id > 0
1063        || !order.delta_neutral_settling_firm.is_empty()
1064        || !order.delta_neutral_clearing_account.is_empty()
1065        || !order.delta_neutral_clearing_intent.is_empty()
1066    {
1067        client.check_server_version(
1068            server_versions::DELTA_NEUTRAL_CONID,
1069            "It does not support delta_neutral parameters: con_id, settling_firm, clearing_account, clearing_intent.",
1070        )?
1071    }
1072
1073    if !order.delta_neutral_open_close.is_empty()
1074        || order.delta_neutral_short_sale
1075        || order.delta_neutral_short_sale_slot > 0
1076        || !order.delta_neutral_designated_location.is_empty()
1077    {
1078        client.check_server_version(
1079            server_versions::DELTA_NEUTRAL_OPEN_CLOSE,
1080            "It does not support delta_neutral parameters: open_close, short_sale, short_saleSlot, designated_location",
1081        )?
1082    }
1083
1084    if (order.scale_price_increment > Some(0.0))
1085        && (order.scale_price_adjust_value.is_some()
1086            || order.scale_price_adjust_interval.is_some()
1087            || order.scale_profit_offset.is_some()
1088            || order.scale_auto_reset
1089            || order.scale_init_position.is_some()
1090            || order.scale_init_fill_qty.is_some()
1091            || order.scale_random_percent)
1092    {
1093        client.check_server_version(
1094                server_versions::SCALE_ORDERS3,
1095                "It does not support Scale order parameters: PriceAdjustValue, PriceAdjustInterval, ProfitOffset, AutoReset, InitPosition, InitFillQty and RandomPercent",
1096            )?
1097    }
1098
1099    if is_bag_order && order.order_combo_legs.iter().any(|combo_leg| combo_leg.price.is_some()) {
1100        client.check_server_version(
1101            server_versions::ORDER_COMBO_LEGS_PRICE,
1102            "It does not support per-leg prices for order combo legs.",
1103        )?
1104    }
1105
1106    if order.trailing_percent.is_some() {
1107        client.check_server_version(server_versions::TRAILING_PERCENT, "It does not support trailing percent parameter.")?
1108    }
1109
1110    if !order.algo_id.is_empty() {
1111        client.check_server_version(server_versions::ALGO_ID, "It does not support algo_id parameter")?
1112    }
1113
1114    if !order.scale_table.is_empty() || !order.active_start_time.is_empty() || !order.active_stop_time.is_empty() {
1115        client.check_server_version(
1116            server_versions::SCALE_TABLE,
1117            "It does not support scale_table, active_start_time nor active_stop_time parameters.",
1118        )?
1119    }
1120
1121    if !order.ext_operator.is_empty() {
1122        client.check_server_version(server_versions::EXT_OPERATOR, "It does not support ext_operator parameter")?
1123    }
1124
1125    if order.cash_qty.is_some() {
1126        client.check_server_version(server_versions::CASH_QTY, "It does not support cash_qty parameter")?
1127    }
1128
1129    if !order.mifid2_execution_trader.is_empty() || !order.mifid2_execution_algo.is_empty() {
1130        client.check_server_version(server_versions::DECISION_MAKER, "It does not support MIFID II execution parameters")?
1131    }
1132
1133    if order.dont_use_auto_price_for_hedge {
1134        client.check_server_version(
1135            server_versions::AUTO_PRICE_FOR_HEDGE,
1136            "It does not support don't use auto price for hedge parameter",
1137        )?
1138    }
1139
1140    if order.is_oms_container {
1141        client.check_server_version(server_versions::ORDER_CONTAINER, "It does not support oms container parameter")?
1142    }
1143
1144    if order.discretionary_up_to_limit_price {
1145        client.check_server_version(server_versions::D_PEG_ORDERS, "It does not support D-Peg orders")?
1146    }
1147
1148    if order.use_price_mgmt_algo {
1149        client.check_server_version(server_versions::PRICE_MGMT_ALGO, "It does not support Use Price Management Algo requests")?
1150    }
1151
1152    if order.duration.is_some() {
1153        client.check_server_version(server_versions::DURATION, "It does not support duration attribute")?
1154    }
1155
1156    if order.post_to_ats.is_some() {
1157        client.check_server_version(server_versions::POST_TO_ATS, "It does not support post_to_ats attribute")?
1158    }
1159
1160    if order.auto_cancel_parent {
1161        client.check_server_version(server_versions::AUTO_CANCEL_PARENT, "It does not support auto_cancel_parent attribute")?
1162    }
1163
1164    if !order.advanced_error_override.is_empty() {
1165        client.check_server_version(
1166            server_versions::ADVANCED_ORDER_REJECT,
1167            "It does not support advanced error override attribute",
1168        )?
1169    }
1170
1171    if !order.manual_order_time.is_empty() {
1172        client.check_server_version(server_versions::MANUAL_ORDER_TIME, "It does not support manual order time attribute")?
1173    }
1174
1175    if order.min_trade_qty.is_some()
1176        || order.min_compete_size.is_some()
1177        || order.compete_against_best_offset.is_some()
1178        || order.mid_offset_at_whole.is_some()
1179        || order.mid_offset_at_half.is_some()
1180    {
1181        client.check_server_version(
1182            server_versions::PEGBEST_PEGMID_OFFSETS,
1183            "It does not support PEG BEST / PEG MID order parameters: minTradeQty, minCompeteSize, competeAgainstBestOffset, midOffsetAtWhole and midOffsetAtHalf",
1184        )?
1185    }
1186
1187    Ok(())
1188}
1189
1190// Verifies that Contract is properly formed.
1191fn verify_order_contract(client: &Client, contract: &Contract, _order_id: i32) -> Result<(), Error> {
1192    if contract
1193        .combo_legs
1194        .iter()
1195        .any(|combo_leg| combo_leg.short_sale_slot != 0 || !combo_leg.designated_location.is_empty())
1196    {
1197        client.check_server_version(server_versions::SSHORT_COMBO_LEGS, "It does not support SSHORT flag for combo legs")?
1198    }
1199
1200    if contract.delta_neutral_contract.is_some() {
1201        client.check_server_version(server_versions::DELTA_NEUTRAL, "It does not support delta-neutral orders")?
1202    }
1203
1204    if contract.contract_id > 0 {
1205        client.check_server_version(server_versions::PLACE_ORDER_CONID, "It does not support contract_id parameter")?
1206    }
1207
1208    if !contract.security_id_type.is_empty() || !contract.security_id.is_empty() {
1209        client.check_server_version(server_versions::SEC_ID_TYPE, "It does not support sec_id_type and sec_id parameters")?
1210    }
1211
1212    if contract.combo_legs.iter().any(|combo_leg| combo_leg.exempt_code != -1) {
1213        client.check_server_version(server_versions::SSHORTX, "It does not support exempt_code parameter")?
1214    }
1215
1216    if !contract.trading_class.is_empty() {
1217        client.check_server_version(
1218            server_versions::TRADING_CLASS,
1219            "It does not support trading_class parameters in place_order",
1220        )?
1221    }
1222
1223    Ok(())
1224}
1225
1226// Cancels an open [Order].
1227pub(crate) fn cancel_order<'a>(client: &'a Client, order_id: i32, manual_order_cancel_time: &str) -> Result<Subscription<'a, CancelOrder>, Error> {
1228    if !manual_order_cancel_time.is_empty() {
1229        client.check_server_version(
1230            server_versions::MANUAL_ORDER_TIME,
1231            "It does not support manual order cancel time attribute",
1232        )?
1233    }
1234
1235    let request = encoders::encode_cancel_order(client.server_version(), order_id, manual_order_cancel_time)?;
1236    let subscription = client.send_order(order_id, request)?;
1237
1238    Ok(Subscription::new(client, subscription, ResponseContext::default()))
1239}
1240
1241/// Enumerates possible results from cancelling an order.
1242#[derive(Debug)]
1243pub enum CancelOrder {
1244    OrderStatus(OrderStatus),
1245    Notice(Notice),
1246}
1247
1248impl DataStream<CancelOrder> for CancelOrder {
1249    fn decode(client: &Client, message: &mut ResponseMessage) -> Result<CancelOrder, Error> {
1250        match message.message_type() {
1251            IncomingMessages::OrderStatus => Ok(CancelOrder::OrderStatus(decoders::decode_order_status(client.server_version, message)?)),
1252            IncomingMessages::Error => Ok(CancelOrder::Notice(Notice::from(message))),
1253            _ => Err(Error::UnexpectedResponse(message.clone())),
1254        }
1255    }
1256}
1257
1258// Cancels all open [Order]s.
1259pub(crate) fn global_cancel(client: &Client) -> Result<(), Error> {
1260    client.check_server_version(server_versions::REQ_GLOBAL_CANCEL, "It does not support global cancel requests.")?;
1261
1262    let message = encoders::encode_global_cancel()?;
1263
1264    let request_id = client.next_request_id();
1265    client.send_order(request_id, message)?;
1266
1267    Ok(())
1268}
1269
1270// Gets next valid order id
1271pub(crate) fn next_valid_order_id(client: &Client) -> Result<i32, Error> {
1272    let message = encoders::encode_next_valid_order_id()?;
1273
1274    let subscription = client.send_shared_request(OutgoingMessages::RequestIds, message)?;
1275
1276    if let Some(Ok(message)) = subscription.next() {
1277        let order_id_index = 2;
1278        let next_order_id = message.peek_int(order_id_index)?;
1279
1280        client.set_next_order_id(next_order_id);
1281
1282        Ok(next_order_id)
1283    } else {
1284        Err(Error::Simple("no response from server".into()))
1285    }
1286}
1287
1288// Requests completed [Order]s.
1289pub(crate) fn completed_orders(client: &Client, api_only: bool) -> Result<Subscription<Orders>, Error> {
1290    client.check_server_version(server_versions::COMPLETED_ORDERS, "It does not support completed orders requests.")?;
1291
1292    let request = encoders::encode_completed_orders(api_only)?;
1293    let subscription = client.send_shared_request(OutgoingMessages::RequestCompletedOrders, request)?;
1294
1295    Ok(Subscription::new(client, subscription, ResponseContext::default()))
1296}
1297
1298/// Enumerates possible results from querying an [Order].
1299#[derive(Debug)]
1300#[allow(clippy::large_enum_variant)]
1301pub enum Orders {
1302    OrderData(OrderData),
1303    OrderStatus(OrderStatus),
1304    Notice(Notice),
1305}
1306
1307impl DataStream<Orders> for Orders {
1308    fn decode(client: &Client, message: &mut ResponseMessage) -> Result<Orders, Error> {
1309        match message.message_type() {
1310            IncomingMessages::CompletedOrder => Ok(Orders::OrderData(decoders::decode_completed_order(
1311                client.server_version,
1312                message.clone(),
1313            )?)),
1314            IncomingMessages::CommissionsReport => Ok(Orders::OrderData(decoders::decode_open_order(client.server_version, message.clone())?)),
1315            IncomingMessages::OpenOrder => Ok(Orders::OrderData(decoders::decode_open_order(client.server_version, message.clone())?)),
1316            IncomingMessages::OrderStatus => Ok(Orders::OrderStatus(decoders::decode_order_status(client.server_version, message)?)),
1317            IncomingMessages::OpenOrderEnd | IncomingMessages::CompletedOrdersEnd => Err(Error::EndOfStream),
1318            IncomingMessages::Error => Ok(Orders::Notice(Notice::from(message))),
1319            _ => Err(Error::UnexpectedResponse(message.clone())),
1320        }
1321    }
1322}
1323
1324/// Requests all open orders places by this specific API client (identified by the API client id).
1325/// For client ID 0, this will bind previous manual TWS orders.
1326///
1327/// # Arguments
1328/// * `client` - [Client] used to communicate with server.
1329///
1330pub(crate) fn open_orders(client: &Client) -> Result<Subscription<Orders>, Error> {
1331    let request = encoders::encode_open_orders()?;
1332    let subscription = client.send_shared_request(OutgoingMessages::RequestOpenOrders, request)?;
1333
1334    Ok(Subscription::new(client, subscription, ResponseContext::default()))
1335}
1336
1337// Requests all *current* open orders in associated accounts at the current moment.
1338// Open orders are returned once; this function does not initiate a subscription.
1339pub(crate) fn all_open_orders(client: &Client) -> Result<Subscription<Orders>, Error> {
1340    let request = encoders::encode_all_open_orders()?;
1341    let subscription = client.send_shared_request(OutgoingMessages::RequestAllOpenOrders, request)?;
1342
1343    Ok(Subscription::new(client, subscription, ResponseContext::default()))
1344}
1345
1346// Requests status updates about future orders placed from TWS. Can only be used with client ID 0.
1347pub(crate) fn auto_open_orders(client: &Client, auto_bind: bool) -> Result<Subscription<Orders>, Error> {
1348    let request = encoders::encode_auto_open_orders(auto_bind)?;
1349    let subscription = client.send_shared_request(OutgoingMessages::RequestAutoOpenOrders, request)?;
1350
1351    Ok(Subscription::new(client, subscription, ResponseContext::default()))
1352}
1353
1354#[derive(Debug, Default)]
1355/// Filter criteria used to determine which execution reports are returned.
1356pub struct ExecutionFilter {
1357    /// The API client which placed the order.
1358    pub client_id: Option<i32>,
1359    /// The account to which the order was allocated to
1360    pub account_code: String,
1361    /// Time from which the executions will be returned yyyymmdd hh:mm:ss
1362    /// Only those executions reported after the specified time will be returned.
1363    pub time: String,
1364    /// The instrument's symbol
1365    pub symbol: String,
1366    /// The Contract's security's type (i.e. STK, OPT...)
1367    pub security_type: String,
1368    /// The exchange at which the execution was produced
1369    pub exchange: String,
1370    /// The Contract's side (BUY or SELL)
1371    pub side: String,
1372}
1373
1374// Requests current day's (since midnight) executions matching the filter.
1375//
1376// Only the current day's executions can be retrieved.
1377// Along with the [ExecutionData], the [CommissionReport] will also be returned.
1378// When requesting executions, a filter can be specified to receive only a subset of them
1379//
1380// # Arguments
1381// * `filter` - filter criteria used to determine which execution reports are returned
1382pub(crate) fn executions(client: &Client, filter: ExecutionFilter) -> Result<Subscription<Executions>, Error> {
1383    let request_id = client.next_request_id();
1384
1385    let request = encoders::encode_executions(client.server_version(), request_id, &filter)?;
1386    let subscription = client.send_request(request_id, request)?;
1387
1388    Ok(Subscription::new(client, subscription, ResponseContext::default()))
1389}
1390
1391/// Enumerates possible results from querying an [Execution].
1392#[derive(Debug)]
1393#[allow(clippy::large_enum_variant)]
1394pub enum Executions {
1395    ExecutionData(ExecutionData),
1396    CommissionReport(CommissionReport),
1397    Notice(Notice),
1398}
1399
1400impl DataStream<Executions> for Executions {
1401    fn decode(client: &Client, message: &mut ResponseMessage) -> Result<Executions, Error> {
1402        match message.message_type() {
1403            IncomingMessages::ExecutionData => Ok(Executions::ExecutionData(decoders::decode_execution_data(
1404                client.server_version,
1405                message,
1406            )?)),
1407            IncomingMessages::CommissionsReport => Ok(Executions::CommissionReport(decoders::decode_commission_report(
1408                client.server_version,
1409                message,
1410            )?)),
1411            IncomingMessages::ExecutionDataEnd => Err(Error::EndOfStream),
1412            IncomingMessages::Error => Ok(Executions::Notice(Notice::from(message))),
1413            _ => Err(Error::UnexpectedResponse(message.clone())),
1414        }
1415    }
1416}
1417
1418#[derive(Debug)]
1419pub enum ExerciseAction {
1420    Exercise = 1,
1421    Lapse = 2,
1422}
1423
1424#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
1425#[allow(clippy::large_enum_variant)]
1426pub enum ExerciseOptions {
1427    OpenOrder(OrderData),
1428    OrderStatus(OrderStatus),
1429    Notice(Notice),
1430}
1431
1432impl DataStream<ExerciseOptions> for ExerciseOptions {
1433    fn decode(client: &Client, message: &mut ResponseMessage) -> Result<ExerciseOptions, Error> {
1434        match message.message_type() {
1435            IncomingMessages::OpenOrder => Ok(ExerciseOptions::OpenOrder(decoders::decode_open_order(
1436                client.server_version,
1437                message.clone(),
1438            )?)),
1439            IncomingMessages::OrderStatus => Ok(ExerciseOptions::OrderStatus(decoders::decode_order_status(
1440                client.server_version,
1441                message,
1442            )?)),
1443            IncomingMessages::Error => Ok(ExerciseOptions::Notice(Notice::from(message))),
1444            _ => Err(Error::UnexpectedResponse(message.clone())),
1445        }
1446    }
1447}
1448
1449pub(crate) fn exercise_options<'a>(
1450    client: &'a Client,
1451    contract: &Contract,
1452    exercise_action: ExerciseAction,
1453    exercise_quantity: i32,
1454    account: &str,
1455    ovrd: bool,
1456    manual_order_time: Option<OffsetDateTime>,
1457) -> Result<Subscription<'a, ExerciseOptions>, Error> {
1458    let request_id = client.next_request_id();
1459
1460    let request = encoders::encode_exercise_options(
1461        client.server_version(),
1462        request_id,
1463        contract,
1464        exercise_action,
1465        exercise_quantity,
1466        account,
1467        ovrd,
1468        manual_order_time,
1469    )?;
1470    let subscription = client.send_request(request_id, request)?;
1471
1472    Ok(Subscription::new(client, subscription, ResponseContext::default()))
1473}