Skip to main content

ibapi/orders/builder/
order_builder.rs

1use super::algo_builders::AlgoParams;
2use super::types::*;
3use super::validation;
4use crate::contracts::Contract;
5use crate::market_data::TradingHours;
6use crate::orders::{Action, Order, OrderComboLeg, OrderCondition, TagValue};
7
8#[cfg(test)]
9mod tests;
10
11/// Builder for creating orders with a fluent interface
12///
13/// All validation is deferred to the build() method to ensure
14/// no silent failures occur during order construction.
15pub struct OrderBuilder<'a, C> {
16    pub(crate) client: &'a C,
17    pub(crate) contract: &'a Contract,
18    action: Option<Action>,
19    quantity: Option<f64>, // Store raw value, validate in build()
20    order_type: Option<OrderType>,
21    limit_price: Option<f64>, // Store raw value, validate in build()
22    stop_price: Option<f64>,  // Store raw value, validate in build()
23    time_in_force: TimeInForce,
24    outside_rth: bool,
25    hidden: bool,
26    transmit: bool,
27    parent_id: Option<i32>,
28    oca_group: Option<String>,
29    oca_type: Option<i32>,
30    account: Option<String>,
31    good_after_time: Option<String>,
32    good_till_date: Option<String>,
33    conditions: Vec<OrderCondition>,
34    algo_strategy: Option<String>,
35    algo_params: Vec<TagValue>,
36    pub(crate) what_if: bool,
37
38    // Advanced fields
39    discretionary_amt: Option<f64>,
40    trailing_percent: Option<f64>,
41    trail_stop_price: Option<f64>,
42    limit_price_offset: Option<f64>,
43    volatility: Option<f64>,
44    volatility_type: Option<i32>,
45    delta: Option<f64>,
46    aux_price: Option<f64>,
47
48    // Special order flags
49    sweep_to_fill: bool,
50    block_order: bool,
51    not_held: bool,
52    all_or_none: bool,
53
54    // Pegged order fields
55    min_trade_qty: Option<i32>,
56    min_compete_size: Option<i32>,
57    compete_against_best_offset: Option<f64>,
58    mid_offset_at_whole: Option<f64>,
59    mid_offset_at_half: Option<f64>,
60
61    // Reference contract fields
62    reference_contract_id: Option<i32>,
63    reference_exchange: Option<String>,
64    stock_ref_price: Option<f64>,
65    stock_range_lower: Option<f64>,
66    stock_range_upper: Option<f64>,
67    reference_change_amount: Option<f64>,
68    pegged_change_amount: Option<f64>,
69    is_pegged_change_amount_decrease: bool,
70
71    // Combo order fields
72    order_combo_legs: Vec<OrderComboLeg>,
73    smart_combo_routing_params: Vec<TagValue>,
74
75    // Cash quantity for FX orders
76    cash_qty: Option<f64>,
77
78    // Manual order time
79    manual_order_time: Option<String>,
80
81    // Auction strategy
82    auction_strategy: Option<i32>,
83
84    // Starting price
85    starting_price: Option<f64>,
86
87    // Hedge type
88    hedge_type: Option<String>,
89}
90
91impl<'a, C> OrderBuilder<'a, C> {
92    /// Creates a new OrderBuilder
93    pub fn new(client: &'a C, contract: &'a Contract) -> Self {
94        Self {
95            client,
96            contract,
97            action: None,
98            quantity: None,
99            order_type: None,
100            limit_price: None,
101            stop_price: None,
102            time_in_force: TimeInForce::Day,
103            outside_rth: false,
104            hidden: false,
105            transmit: true,
106            parent_id: None,
107            oca_group: None,
108            oca_type: None,
109            account: None,
110            good_after_time: None,
111            good_till_date: None,
112            conditions: Vec::new(),
113            algo_strategy: None,
114            algo_params: Vec::new(),
115            what_if: false,
116            discretionary_amt: None,
117            trailing_percent: None,
118            trail_stop_price: None,
119            limit_price_offset: None,
120            volatility: None,
121            volatility_type: None,
122            delta: None,
123            aux_price: None,
124            sweep_to_fill: false,
125            block_order: false,
126            not_held: false,
127            all_or_none: false,
128            min_trade_qty: None,
129            min_compete_size: None,
130            compete_against_best_offset: None,
131            mid_offset_at_whole: None,
132            mid_offset_at_half: None,
133            reference_contract_id: None,
134            reference_exchange: None,
135            stock_ref_price: None,
136            stock_range_lower: None,
137            stock_range_upper: None,
138            reference_change_amount: None,
139            pegged_change_amount: None,
140            is_pegged_change_amount_decrease: false,
141            order_combo_legs: Vec::new(),
142            smart_combo_routing_params: Vec::new(),
143            cash_qty: None,
144            manual_order_time: None,
145            auction_strategy: None,
146            starting_price: None,
147            hedge_type: None,
148        }
149    }
150
151    // Action methods
152
153    /// Set order to buy the specified quantity
154    pub fn buy(mut self, quantity: impl Into<f64>) -> Self {
155        self.action = Some(Action::Buy);
156        self.quantity = Some(quantity.into());
157        self
158    }
159
160    /// Set order to sell the specified quantity
161    pub fn sell(mut self, quantity: impl Into<f64>) -> Self {
162        self.action = Some(Action::Sell);
163        self.quantity = Some(quantity.into());
164        self
165    }
166
167    // Order type methods
168
169    /// Create a market order
170    pub fn market(mut self) -> Self {
171        self.order_type = Some(OrderType::Market);
172        self
173    }
174
175    /// Create a limit order at the specified price
176    pub fn limit(mut self, price: impl Into<f64>) -> Self {
177        self.order_type = Some(OrderType::Limit);
178        self.limit_price = Some(price.into());
179        self
180    }
181
182    /// Create a stop order at the specified stop price
183    pub fn stop(mut self, stop_price: impl Into<f64>) -> Self {
184        self.order_type = Some(OrderType::Stop);
185        self.stop_price = Some(stop_price.into());
186        self
187    }
188
189    /// Create a stop-limit order
190    pub fn stop_limit(mut self, stop_price: impl Into<f64>, limit_price: impl Into<f64>) -> Self {
191        self.order_type = Some(OrderType::StopLimit);
192        self.stop_price = Some(stop_price.into());
193        self.limit_price = Some(limit_price.into());
194        self
195    }
196
197    /// Create a trailing stop order
198    pub fn trailing_stop(mut self, trailing_percent: f64, stop_price: impl Into<f64>) -> Self {
199        self.order_type = Some(OrderType::TrailingStop);
200        self.trailing_percent = Some(trailing_percent);
201        self.trail_stop_price = Some(stop_price.into());
202        self
203    }
204
205    /// Set a custom order type
206    pub fn order_type(mut self, order_type: OrderType) -> Self {
207        self.order_type = Some(order_type);
208        self
209    }
210
211    /// Create a trailing stop limit order
212    pub fn trailing_stop_limit(mut self, trailing_percent: f64, stop_price: impl Into<f64>, limit_offset: f64) -> Self {
213        self.order_type = Some(OrderType::TrailingStopLimit);
214        self.trailing_percent = Some(trailing_percent);
215        self.trail_stop_price = Some(stop_price.into());
216        self.limit_price_offset = Some(limit_offset);
217        self
218    }
219
220    /// Market if Touched - triggers market order when price is touched
221    pub fn market_if_touched(mut self, trigger_price: impl Into<f64>) -> Self {
222        self.order_type = Some(OrderType::MarketIfTouched);
223        self.aux_price = Some(trigger_price.into());
224        self
225    }
226
227    /// Limit if Touched - triggers limit order when price is touched
228    pub fn limit_if_touched(mut self, trigger_price: impl Into<f64>, limit_price: impl Into<f64>) -> Self {
229        self.order_type = Some(OrderType::LimitIfTouched);
230        self.aux_price = Some(trigger_price.into());
231        self.limit_price = Some(limit_price.into());
232        self
233    }
234
235    /// Market to Limit - starts as market order, remainder becomes limit
236    pub fn market_to_limit(mut self) -> Self {
237        self.order_type = Some(OrderType::MarketToLimit);
238        self
239    }
240
241    /// Discretionary order - limit order with hidden discretionary amount
242    pub fn discretionary(mut self, limit_price: impl Into<f64>, discretionary_amt: f64) -> Self {
243        self.order_type = Some(OrderType::Limit);
244        self.limit_price = Some(limit_price.into());
245        self.discretionary_amt = Some(discretionary_amt);
246        self
247    }
248
249    /// Sweep to Fill - prioritizes speed of execution over price
250    pub fn sweep_to_fill(mut self, limit_price: impl Into<f64>) -> Self {
251        self.order_type = Some(OrderType::Limit);
252        self.limit_price = Some(limit_price.into());
253        self.sweep_to_fill = true;
254        self
255    }
256
257    /// Block order - for large volume option orders (min 50 contracts)
258    pub fn block(mut self, limit_price: impl Into<f64>) -> Self {
259        self.order_type = Some(OrderType::Limit);
260        self.limit_price = Some(limit_price.into());
261        self.block_order = true;
262        self
263    }
264
265    /// Midprice order - fills at midpoint between bid/ask or better
266    pub fn midprice(mut self, price_cap: Option<f64>) -> Self {
267        self.order_type = Some(OrderType::Midprice);
268        self.limit_price = price_cap;
269        self
270    }
271
272    /// Relative/Pegged-to-Primary - seeks more aggressive price than NBBO
273    pub fn relative(mut self, offset: f64, price_cap: Option<f64>) -> Self {
274        self.order_type = Some(OrderType::Relative);
275        self.aux_price = Some(offset);
276        self.limit_price = price_cap;
277        self
278    }
279
280    /// Passive Relative - seeks less aggressive price than NBBO
281    pub fn passive_relative(mut self, offset: f64) -> Self {
282        self.order_type = Some(OrderType::PassiveRelative);
283        self.aux_price = Some(offset);
284        self
285    }
286
287    /// At Auction - for pre-market opening period execution
288    pub fn at_auction(mut self, price: impl Into<f64>) -> Self {
289        self.order_type = Some(OrderType::AtAuction);
290        self.limit_price = Some(price.into());
291        self.time_in_force = TimeInForce::Auction;
292        self
293    }
294
295    /// Market on Close - executes as market order at or near closing price
296    pub fn market_on_close(mut self) -> Self {
297        self.order_type = Some(OrderType::MarketOnClose);
298        self
299    }
300
301    /// Limit on Close - executes as limit order at close if price is met
302    pub fn limit_on_close(mut self, limit_price: impl Into<f64>) -> Self {
303        self.order_type = Some(OrderType::LimitOnClose);
304        self.limit_price = Some(limit_price.into());
305        self
306    }
307
308    /// Market on Open - executes as market order at market open
309    pub fn market_on_open(mut self) -> Self {
310        self.order_type = Some(OrderType::Market);
311        self.time_in_force = TimeInForce::OpeningAuction;
312        self
313    }
314
315    /// Limit on Open - executes as limit order at market open
316    pub fn limit_on_open(mut self, limit_price: impl Into<f64>) -> Self {
317        self.order_type = Some(OrderType::Limit);
318        self.limit_price = Some(limit_price.into());
319        self.time_in_force = TimeInForce::OpeningAuction;
320        self
321    }
322
323    /// Market with Protection - market order with protection against extreme price movements (futures only)
324    pub fn market_with_protection(mut self) -> Self {
325        self.order_type = Some(OrderType::MarketWithProtection);
326        self
327    }
328
329    /// Stop with Protection - stop order with protection against extreme price movements (futures only)
330    pub fn stop_with_protection(mut self, stop_price: impl Into<f64>) -> Self {
331        self.order_type = Some(OrderType::StopWithProtection);
332        self.stop_price = Some(stop_price.into());
333        self
334    }
335
336    // Time in force methods
337
338    /// Set time in force for the order
339    pub fn time_in_force(mut self, tif: TimeInForce) -> Self {
340        self.time_in_force = tif;
341        self
342    }
343
344    /// Order valid for the day only
345    pub fn day_order(mut self) -> Self {
346        self.time_in_force = TimeInForce::Day;
347        self
348    }
349
350    /// Good till cancelled order
351    pub fn good_till_cancel(mut self) -> Self {
352        self.time_in_force = TimeInForce::GoodTillCancel;
353        self
354    }
355
356    /// Good till specific date
357    pub fn good_till_date(mut self, date: impl Into<String>) -> Self {
358        let date_str = date.into();
359        self.time_in_force = TimeInForce::GoodTillDate { date: date_str.clone() };
360        self.good_till_date = Some(date_str);
361        self
362    }
363
364    /// Fill or kill order
365    pub fn fill_or_kill(mut self) -> Self {
366        self.time_in_force = TimeInForce::FillOrKill;
367        self
368    }
369
370    /// Immediate or cancel order
371    pub fn immediate_or_cancel(mut self) -> Self {
372        self.time_in_force = TimeInForce::ImmediateOrCancel;
373        self
374    }
375
376    // Trading hours
377
378    /// Allow order execution outside regular trading hours
379    pub fn outside_rth(mut self) -> Self {
380        self.outside_rth = true;
381        self
382    }
383
384    /// Restrict order to regular trading hours only
385    pub fn regular_hours_only(mut self) -> Self {
386        self.outside_rth = false;
387        self
388    }
389
390    /// Set trading hours preference
391    pub fn trading_hours(mut self, hours: TradingHours) -> Self {
392        self.outside_rth = matches!(hours, TradingHours::Extended);
393        self
394    }
395
396    // Order attributes
397
398    /// Hide order from market depth (only works for NASDAQ-routed orders)
399    pub fn hidden(mut self) -> Self {
400        self.hidden = true;
401        self
402    }
403
404    /// Set account for order
405    pub fn account(mut self, account: impl Into<String>) -> Self {
406        self.account = Some(account.into());
407        self
408    }
409
410    /// Set parent order ID for attached orders
411    pub fn parent(mut self, parent_id: i32) -> Self {
412        self.parent_id = Some(parent_id);
413        self
414    }
415
416    /// Set OCA group
417    pub fn oca_group(mut self, group: impl Into<String>, oca_type: i32) -> Self {
418        self.oca_group = Some(group.into());
419        self.oca_type = Some(oca_type);
420        self
421    }
422
423    /// Do not transmit order immediately
424    pub fn do_not_transmit(mut self) -> Self {
425        self.transmit = false;
426        self
427    }
428
429    /// Create bracket orders with take profit and stop loss
430    pub fn bracket(self) -> BracketOrderBuilder<'a, C> {
431        BracketOrderBuilder::new(self)
432    }
433
434    // Conditional orders
435
436    /// Add a condition to the order.
437    ///
438    /// The first condition is always treated as AND. Use `and_condition()` or `or_condition()`
439    /// for subsequent conditions to specify the logical relationship.
440    ///
441    /// # Example
442    ///
443    /// ```ignore
444    /// # async fn example() -> Result<(), Box<dyn std::error::Error>> {
445    /// # use ibapi::client::Client;
446    /// # use ibapi::contracts::Contract;
447    /// # let client = Client::connect("127.0.0.1:4002", 100).await?;
448    /// # let contract = Contract::stock("AAPL").build();
449    /// use ibapi::orders::builder::price;
450    ///
451    /// let order_id = client.order(&contract)
452    ///     .buy(100)
453    ///     .market()
454    ///     .condition(price(265598, "SMART").greater_than(150.0))
455    ///     .submit().await?;
456    /// # Ok(())
457    /// # }
458    /// ```
459    pub fn condition(mut self, condition: impl Into<OrderCondition>) -> Self {
460        let mut cond = condition.into();
461        set_conjunction(&mut cond, true);
462        self.conditions.push(cond);
463        self
464    }
465
466    /// Add a condition that must be met along with previous conditions (AND logic).
467    ///
468    /// # Example
469    ///
470    /// ```ignore
471    /// # async fn example() -> Result<(), Box<dyn std::error::Error>> {
472    /// # use ibapi::client::Client;
473    /// # use ibapi::contracts::Contract;
474    /// # let client = Client::connect("127.0.0.1:4002", 100).await?;
475    /// # let contract = Contract::stock("AAPL").build();
476    /// use ibapi::orders::builder::{price, margin};
477    ///
478    /// let order_id = client.order(&contract)
479    ///     .buy(100)
480    ///     .market()
481    ///     .condition(price(265598, "SMART").greater_than(150.0))
482    ///     .and_condition(margin().greater_than(30))
483    ///     .submit().await?;
484    /// # Ok(())
485    /// # }
486    /// ```
487    pub fn and_condition(mut self, condition: impl Into<OrderCondition>) -> Self {
488        if let Some(prev) = self.conditions.last_mut() {
489            set_conjunction(prev, true);
490        }
491        self.conditions.push(condition.into());
492        self
493    }
494
495    /// Add a condition where either this OR previous conditions trigger the order (OR logic).
496    ///
497    /// # Example
498    ///
499    /// ```ignore
500    /// # async fn example() -> Result<(), Box<dyn std::error::Error>> {
501    /// # use ibapi::client::Client;
502    /// # use ibapi::contracts::Contract;
503    /// # let client = Client::connect("127.0.0.1:4002", 100).await?;
504    /// # let contract = Contract::stock("AAPL").build();
505    /// use ibapi::orders::builder::{price, volume};
506    ///
507    /// let order_id = client.order(&contract)
508    ///     .buy(100)
509    ///     .market()
510    ///     .condition(price(265598, "SMART").less_than(100.0))
511    ///     .or_condition(volume(265598, "SMART").greater_than(50_000_000))
512    ///     .submit().await?;
513    /// # Ok(())
514    /// # }
515    /// ```
516    pub fn or_condition(mut self, condition: impl Into<OrderCondition>) -> Self {
517        if let Some(prev) = self.conditions.last_mut() {
518            set_conjunction(prev, false);
519        }
520        self.conditions.push(condition.into());
521        self
522    }
523
524    // Algorithmic trading
525
526    /// Set algorithm strategy and parameters.
527    ///
528    /// Accepts either a strategy name (string) or an algo builder.
529    ///
530    /// # Example with builder
531    ///
532    /// ```ignore
533    /// # async fn example() -> Result<(), Box<dyn std::error::Error>> {
534    /// # use ibapi::client::Client;
535    /// # use ibapi::contracts::Contract;
536    /// # let client = Client::connect("127.0.0.1:4002", 100).await?;
537    /// # let contract = Contract::stock("AAPL").build();
538    /// use ibapi::orders::builder::vwap;
539    ///
540    /// let order_id = client.order(&contract)
541    ///     .buy(1000)
542    ///     .limit(150.0)
543    ///     .algo(vwap()
544    ///         .max_pct_vol(0.2)
545    ///         .start_time("09:00:00 US/Eastern")
546    ///         .end_time("16:00:00 US/Eastern")
547    ///         .build()?)
548    ///     .submit().await?;
549    /// # Ok(())
550    /// # }
551    /// ```
552    ///
553    /// # Example with string (for custom strategies)
554    ///
555    /// ```ignore
556    /// # async fn example() -> Result<(), Box<dyn std::error::Error>> {
557    /// # use ibapi::client::Client;
558    /// # use ibapi::contracts::Contract;
559    /// # let client = Client::connect("127.0.0.1:4002", 100).await?;
560    /// # let contract = Contract::stock("AAPL").build();
561    /// let order_id = client.order(&contract)
562    ///     .buy(1000)
563    ///     .limit(150.0)
564    ///     .algo("Vwap")
565    ///     .algo_param("maxPctVol", "0.2")
566    ///     .submit().await?;
567    /// # Ok(())
568    /// # }
569    /// ```
570    pub fn algo(mut self, algo: impl Into<AlgoParams>) -> Self {
571        let params = algo.into();
572        self.algo_strategy = Some(params.strategy);
573        self.algo_params.extend(params.params);
574        self
575    }
576
577    /// Add algorithm parameter.
578    ///
579    /// Use this to add individual parameters when using a strategy name string.
580    /// When using algo builders, parameters are set via the builder methods.
581    pub fn algo_param(mut self, key: impl Into<String>, value: impl Into<String>) -> Self {
582        self.algo_params.push(TagValue {
583            tag: key.into(),
584            value: value.into(),
585        });
586        self
587    }
588
589    // What-if orders
590
591    /// Mark as what-if order for margin/commission calculation
592    pub fn what_if(mut self) -> Self {
593        self.what_if = true;
594        self
595    }
596
597    // Additional order attributes
598
599    /// Set volatility for volatility orders
600    pub fn volatility(mut self, volatility: f64) -> Self {
601        self.volatility = Some(volatility);
602        self
603    }
604
605    /// Mark order as not held
606    pub fn not_held(mut self) -> Self {
607        self.not_held = true;
608        self
609    }
610
611    /// Mark order as all or none
612    pub fn all_or_none(mut self) -> Self {
613        self.all_or_none = true;
614        self
615    }
616
617    /// Set good after time
618    pub fn good_after_time(mut self, time: impl Into<String>) -> Self {
619        self.good_after_time = Some(time.into());
620        self
621    }
622
623    /// Set good till time (stored in good_till_date field)
624    pub fn good_till_time(mut self, time: impl Into<String>) -> Self {
625        self.good_till_date = Some(time.into());
626        self
627    }
628
629    // Pegged order configuration
630
631    /// Set minimum trade quantity for pegged orders
632    pub fn min_trade_qty(mut self, qty: i32) -> Self {
633        self.min_trade_qty = Some(qty);
634        self
635    }
636
637    /// Set minimum compete size for pegged orders
638    pub fn min_compete_size(mut self, size: i32) -> Self {
639        self.min_compete_size = Some(size);
640        self
641    }
642
643    /// Set compete against best offset for pegged orders
644    pub fn compete_against_best_offset(mut self, offset: f64) -> Self {
645        self.compete_against_best_offset = Some(offset);
646        self
647    }
648
649    /// Set mid offset at whole for pegged orders
650    pub fn mid_offset_at_whole(mut self, offset: f64) -> Self {
651        self.mid_offset_at_whole = Some(offset);
652        self
653    }
654
655    /// Set mid offset at half for pegged orders
656    pub fn mid_offset_at_half(mut self, offset: f64) -> Self {
657        self.mid_offset_at_half = Some(offset);
658        self
659    }
660
661    // Build methods
662
663    /// Build the Order struct with full validation
664    pub fn build(self) -> Result<Order, ValidationError> {
665        // Validate required fields
666        let action = self.action.ok_or(ValidationError::MissingRequiredField("action"))?;
667        let quantity_raw = self.quantity.ok_or(ValidationError::MissingRequiredField("quantity"))?;
668        let order_type = self.order_type.ok_or(ValidationError::MissingRequiredField("order_type"))?;
669
670        // Validate quantity
671        let quantity = Quantity::new(quantity_raw)?;
672
673        // Validate prices based on order type
674        let limit_price = if order_type.requires_limit_price() {
675            let price_raw = self.limit_price.ok_or(ValidationError::MissingRequiredField("limit_price"))?;
676            Some(Price::new(price_raw)?)
677        } else if let Some(price_raw) = self.limit_price {
678            Some(Price::new(price_raw)?)
679        } else {
680            None
681        };
682
683        let stop_price = match order_type {
684            OrderType::Stop | OrderType::StopLimit => {
685                let price_raw = self.stop_price.ok_or(ValidationError::MissingRequiredField("stop_price"))?;
686                Some(Price::new(price_raw)?)
687            }
688            _ => {
689                if let Some(price_raw) = self.stop_price {
690                    Some(Price::new(price_raw)?)
691                } else {
692                    None
693                }
694            }
695        };
696
697        let trail_stop_price = match order_type {
698            OrderType::TrailingStop | OrderType::TrailingStopLimit => {
699                if self.trailing_percent.is_none() && self.trail_stop_price.is_none() {
700                    return Err(ValidationError::MissingRequiredField("trailing amount or stop price"));
701                }
702                if let Some(price_raw) = self.trail_stop_price {
703                    Some(Price::new(price_raw)?)
704                } else {
705                    None
706                }
707            }
708            _ => None,
709        };
710
711        // Validate volatility for volatility orders
712        if order_type == OrderType::Volatility && self.volatility.is_none() {
713            return Err(ValidationError::MissingRequiredField("volatility"));
714        }
715
716        // Validate time in force specific requirements
717        if let TimeInForce::GoodTillDate { .. } = &self.time_in_force {
718            if self.good_till_date.is_none() {
719                return Err(ValidationError::MissingRequiredField("good_till_date"));
720            }
721        }
722
723        // Build the order
724        let mut order = Order {
725            action,
726            total_quantity: quantity.value(),
727            order_type: order_type.as_str().to_string(),
728            ..Default::default()
729        };
730
731        // Set prices
732        if let Some(price) = limit_price {
733            order.limit_price = Some(price.value());
734        }
735
736        if let Some(price) = stop_price {
737            order.aux_price = Some(price.value());
738        }
739
740        if let Some(price) = trail_stop_price {
741            order.trail_stop_price = Some(price.value());
742        }
743
744        if let Some(percent) = self.trailing_percent {
745            order.trailing_percent = Some(percent);
746        }
747
748        if let Some(offset) = self.limit_price_offset {
749            order.limit_price_offset = Some(offset);
750        }
751
752        // Set time in force
753        order.tif = crate::orders::TimeInForce::from(self.time_in_force.as_str());
754        if let TimeInForce::GoodTillDate { date } = &self.time_in_force {
755            order.good_till_date = date.clone();
756        }
757
758        // Set other fields
759        order.outside_rth = self.outside_rth;
760        order.hidden = self.hidden;
761        order.transmit = self.transmit;
762
763        if let Some(parent_id) = self.parent_id {
764            order.parent_id = parent_id;
765        }
766
767        if let Some(group) = self.oca_group {
768            order.oca_group = group;
769            order.oca_type = self.oca_type.unwrap_or(0).into();
770        }
771
772        if let Some(account) = self.account {
773            order.account = account;
774        }
775
776        if let Some(time) = self.good_after_time {
777            order.good_after_time = time;
778        }
779
780        // Set good_till_date if set via good_till_time method
781        if let Some(date_time) = self.good_till_date {
782            if !matches!(self.time_in_force, TimeInForce::GoodTillDate { .. }) {
783                // If not already set via time_in_force
784                order.good_till_date = date_time;
785            }
786        }
787
788        if let Some(strategy) = self.algo_strategy {
789            order.algo_strategy = strategy;
790            order.algo_params = self.algo_params;
791        }
792
793        order.what_if = self.what_if;
794
795        // Set advanced fields
796        if let Some(amt) = self.discretionary_amt {
797            order.discretionary_amt = amt;
798        }
799
800        if let Some(vol) = self.volatility {
801            order.volatility = Some(vol);
802            order.volatility_type = self.volatility_type.map(|v| v.into());
803        }
804
805        if let Some(delta) = self.delta {
806            order.delta = Some(delta);
807        }
808
809        if let Some(aux) = self.aux_price {
810            // Only set if not already set by stop price
811            if order.aux_price.is_none() {
812                order.aux_price = Some(aux);
813            }
814        }
815
816        // Set special flags
817        order.sweep_to_fill = self.sweep_to_fill;
818        order.block_order = self.block_order;
819        order.not_held = self.not_held;
820        order.all_or_none = self.all_or_none;
821
822        // Set pegged order fields
823        if let Some(qty) = self.min_trade_qty {
824            order.min_trade_qty = Some(qty);
825        }
826
827        if let Some(size) = self.min_compete_size {
828            order.min_compete_size = Some(size);
829        }
830
831        if let Some(offset) = self.compete_against_best_offset {
832            order.compete_against_best_offset = Some(offset);
833        }
834
835        if let Some(offset) = self.mid_offset_at_whole {
836            order.mid_offset_at_whole = Some(offset);
837        }
838
839        if let Some(offset) = self.mid_offset_at_half {
840            order.mid_offset_at_half = Some(offset);
841        }
842
843        // Set conditions
844        if !self.conditions.is_empty() {
845            order.conditions = self.conditions;
846        }
847
848        // Set reference contract fields for pegged to benchmark orders
849        if let Some(id) = self.reference_contract_id {
850            order.reference_contract_id = id;
851        }
852
853        if let Some(exchange) = self.reference_exchange {
854            order.reference_exchange = exchange;
855        }
856
857        if let Some(price) = self.stock_ref_price {
858            order.stock_ref_price = Some(price);
859        }
860
861        if let Some(lower) = self.stock_range_lower {
862            order.stock_range_lower = Some(lower);
863        }
864
865        if let Some(upper) = self.stock_range_upper {
866            order.stock_range_upper = Some(upper);
867        }
868
869        if let Some(amount) = self.reference_change_amount {
870            order.reference_change_amount = Some(amount);
871        }
872
873        if let Some(amount) = self.pegged_change_amount {
874            order.pegged_change_amount = Some(amount);
875        }
876
877        if self.is_pegged_change_amount_decrease {
878            order.is_pegged_change_amount_decrease = true;
879        }
880
881        // Set combo order fields
882        if !self.order_combo_legs.is_empty() {
883            order.order_combo_legs = self.order_combo_legs;
884        }
885
886        if !self.smart_combo_routing_params.is_empty() {
887            order.smart_combo_routing_params = self.smart_combo_routing_params;
888        }
889
890        // Set cash quantity for FX orders
891        if let Some(qty) = self.cash_qty {
892            order.cash_qty = Some(qty);
893        }
894
895        // Set manual order time
896        if let Some(time) = self.manual_order_time {
897            order.manual_order_time = time;
898        }
899
900        // Set auction strategy
901        if let Some(strategy) = self.auction_strategy {
902            order.auction_strategy = Some(strategy.into());
903        }
904
905        // Set starting price
906        if let Some(price) = self.starting_price {
907            order.starting_price = Some(price);
908        }
909
910        // Set hedge type
911        if let Some(hedge_type) = self.hedge_type {
912            order.hedge_type = hedge_type;
913        }
914
915        Ok(order)
916    }
917}
918
919/// Helper function to set conjunction flag on OrderCondition enum
920fn set_conjunction(condition: &mut OrderCondition, is_conjunction: bool) {
921    match condition {
922        OrderCondition::Price(c) => c.is_conjunction = is_conjunction,
923        OrderCondition::Time(c) => c.is_conjunction = is_conjunction,
924        OrderCondition::Margin(c) => c.is_conjunction = is_conjunction,
925        OrderCondition::Execution(c) => c.is_conjunction = is_conjunction,
926        OrderCondition::Volume(c) => c.is_conjunction = is_conjunction,
927        OrderCondition::PercentChange(c) => c.is_conjunction = is_conjunction,
928    }
929}
930
931/// Entry order type for bracket orders
932#[derive(Default)]
933enum BracketEntryType {
934    #[default]
935    None,
936    Limit(f64),
937    Market,
938}
939
940/// Builder for bracket orders
941pub struct BracketOrderBuilder<'a, C> {
942    pub(crate) parent_builder: OrderBuilder<'a, C>,
943    entry_type: BracketEntryType,
944    take_profit_price: Option<f64>,
945    stop_loss_price: Option<f64>,
946}
947
948impl<'a, C> BracketOrderBuilder<'a, C> {
949    fn new(parent_builder: OrderBuilder<'a, C>) -> Self {
950        Self {
951            parent_builder,
952            entry_type: BracketEntryType::None,
953            take_profit_price: None,
954            stop_loss_price: None,
955        }
956    }
957
958    /// Set entry as market order (immediate execution)
959    pub fn entry_market(mut self) -> Self {
960        self.entry_type = BracketEntryType::Market;
961        self
962    }
963
964    /// Set entry limit price
965    pub fn entry_limit(mut self, price: impl Into<f64>) -> Self {
966        self.entry_type = BracketEntryType::Limit(price.into());
967        self
968    }
969
970    /// Set take profit price
971    pub fn take_profit(mut self, price: impl Into<f64>) -> Self {
972        self.take_profit_price = Some(price.into());
973        self
974    }
975
976    /// Set stop loss price
977    pub fn stop_loss(mut self, price: impl Into<f64>) -> Self {
978        self.stop_loss_price = Some(price.into());
979        self
980    }
981
982    /// Build bracket orders with full validation
983    pub fn build(mut self) -> Result<Vec<Order>, ValidationError> {
984        // Validate and convert take profit and stop loss prices
985        let take_profit_raw = self.take_profit_price.ok_or(ValidationError::MissingRequiredField("take_profit"))?;
986        let stop_loss_raw = self.stop_loss_price.ok_or(ValidationError::MissingRequiredField("stop_loss"))?;
987
988        let take_profit = Price::new(take_profit_raw)?;
989        let stop_loss = Price::new(stop_loss_raw)?;
990
991        // Set order type based on entry type
992        match self.entry_type {
993            BracketEntryType::None => {
994                return Err(ValidationError::MissingRequiredField("entry (use entry_limit() or entry_market())"));
995            }
996            BracketEntryType::Limit(price) => {
997                let entry_price = Price::new(price)?;
998                // Validate bracket order prices
999                validation::validate_bracket_prices(
1000                    self.parent_builder.action.as_ref(),
1001                    entry_price.value(),
1002                    take_profit.value(),
1003                    stop_loss.value(),
1004                )?;
1005                self.parent_builder.order_type = Some(OrderType::Limit);
1006                self.parent_builder.limit_price = Some(entry_price.value());
1007            }
1008            BracketEntryType::Market => {
1009                // Skip price relationship validation for market orders
1010                self.parent_builder.order_type = Some(OrderType::Market);
1011            }
1012        }
1013
1014        // Build parent order
1015        let mut parent = self.parent_builder.build()?;
1016        parent.transmit = false;
1017
1018        // Build take profit order
1019        let take_profit_order = Order {
1020            action: parent.action.reverse(),
1021            order_type: "LMT".to_string(),
1022            total_quantity: parent.total_quantity,
1023            limit_price: Some(take_profit.value()),
1024            parent_id: parent.order_id,
1025            transmit: false,
1026            outside_rth: parent.outside_rth,
1027            ..Default::default()
1028        };
1029
1030        // Build stop loss order
1031        let stop_loss_order = Order {
1032            action: parent.action.reverse(),
1033            order_type: "STP".to_string(),
1034            total_quantity: parent.total_quantity,
1035            aux_price: Some(stop_loss.value()),
1036            parent_id: parent.order_id,
1037            transmit: true,
1038            outside_rth: parent.outside_rth,
1039            ..Default::default()
1040        };
1041
1042        Ok(vec![parent, take_profit_order, stop_loss_order])
1043    }
1044}