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}