use super::algo_builders::AlgoParams;
use super::types::*;
use super::validation;
use crate::contracts::Contract;
use crate::market_data::TradingHours;
use crate::orders::{Action, Order, OrderComboLeg, OrderCondition, TagValue};
#[cfg(test)]
mod tests;
pub struct OrderBuilder<'a, C> {
pub(crate) client: &'a C,
pub(crate) contract: &'a Contract,
action: Option<Action>,
quantity: Option<f64>, order_type: Option<OrderType>,
limit_price: Option<f64>, stop_price: Option<f64>, time_in_force: TimeInForce,
outside_rth: bool,
hidden: bool,
transmit: bool,
parent_id: Option<i32>,
oca_group: Option<String>,
oca_type: Option<i32>,
account: Option<String>,
good_after_time: Option<String>,
good_till_date: Option<String>,
conditions: Vec<OrderCondition>,
algo_strategy: Option<String>,
algo_params: Vec<TagValue>,
pub(crate) what_if: bool,
discretionary_amt: Option<f64>,
trailing_percent: Option<f64>,
trail_stop_price: Option<f64>,
limit_price_offset: Option<f64>,
volatility: Option<f64>,
volatility_type: Option<i32>,
delta: Option<f64>,
aux_price: Option<f64>,
sweep_to_fill: bool,
block_order: bool,
not_held: bool,
all_or_none: bool,
min_trade_qty: Option<i32>,
min_compete_size: Option<i32>,
compete_against_best_offset: Option<f64>,
mid_offset_at_whole: Option<f64>,
mid_offset_at_half: Option<f64>,
reference_contract_id: Option<i32>,
reference_exchange: Option<String>,
stock_ref_price: Option<f64>,
stock_range_lower: Option<f64>,
stock_range_upper: Option<f64>,
reference_change_amount: Option<f64>,
pegged_change_amount: Option<f64>,
is_pegged_change_amount_decrease: bool,
order_combo_legs: Vec<OrderComboLeg>,
smart_combo_routing_params: Vec<TagValue>,
cash_qty: Option<f64>,
manual_order_time: Option<String>,
auction_strategy: Option<i32>,
starting_price: Option<f64>,
hedge_type: Option<String>,
}
impl<'a, C> OrderBuilder<'a, C> {
pub fn new(client: &'a C, contract: &'a Contract) -> Self {
Self {
client,
contract,
action: None,
quantity: None,
order_type: None,
limit_price: None,
stop_price: None,
time_in_force: TimeInForce::Day,
outside_rth: false,
hidden: false,
transmit: true,
parent_id: None,
oca_group: None,
oca_type: None,
account: None,
good_after_time: None,
good_till_date: None,
conditions: Vec::new(),
algo_strategy: None,
algo_params: Vec::new(),
what_if: false,
discretionary_amt: None,
trailing_percent: None,
trail_stop_price: None,
limit_price_offset: None,
volatility: None,
volatility_type: None,
delta: None,
aux_price: None,
sweep_to_fill: false,
block_order: false,
not_held: false,
all_or_none: false,
min_trade_qty: None,
min_compete_size: None,
compete_against_best_offset: None,
mid_offset_at_whole: None,
mid_offset_at_half: None,
reference_contract_id: None,
reference_exchange: None,
stock_ref_price: None,
stock_range_lower: None,
stock_range_upper: None,
reference_change_amount: None,
pegged_change_amount: None,
is_pegged_change_amount_decrease: false,
order_combo_legs: Vec::new(),
smart_combo_routing_params: Vec::new(),
cash_qty: None,
manual_order_time: None,
auction_strategy: None,
starting_price: None,
hedge_type: None,
}
}
pub fn buy(mut self, quantity: impl Into<f64>) -> Self {
self.action = Some(Action::Buy);
self.quantity = Some(quantity.into());
self
}
pub fn sell(mut self, quantity: impl Into<f64>) -> Self {
self.action = Some(Action::Sell);
self.quantity = Some(quantity.into());
self
}
pub fn market(mut self) -> Self {
self.order_type = Some(OrderType::Market);
self
}
pub fn limit(mut self, price: impl Into<f64>) -> Self {
self.order_type = Some(OrderType::Limit);
self.limit_price = Some(price.into());
self
}
pub fn stop(mut self, stop_price: impl Into<f64>) -> Self {
self.order_type = Some(OrderType::Stop);
self.stop_price = Some(stop_price.into());
self
}
pub fn stop_limit(mut self, stop_price: impl Into<f64>, limit_price: impl Into<f64>) -> Self {
self.order_type = Some(OrderType::StopLimit);
self.stop_price = Some(stop_price.into());
self.limit_price = Some(limit_price.into());
self
}
pub fn trailing_stop(mut self, trailing_percent: f64, stop_price: impl Into<f64>) -> Self {
self.order_type = Some(OrderType::TrailingStop);
self.trailing_percent = Some(trailing_percent);
self.trail_stop_price = Some(stop_price.into());
self
}
pub fn order_type(mut self, order_type: OrderType) -> Self {
self.order_type = Some(order_type);
self
}
pub fn trailing_stop_limit(mut self, trailing_percent: f64, stop_price: impl Into<f64>, limit_offset: f64) -> Self {
self.order_type = Some(OrderType::TrailingStopLimit);
self.trailing_percent = Some(trailing_percent);
self.trail_stop_price = Some(stop_price.into());
self.limit_price_offset = Some(limit_offset);
self
}
pub fn market_if_touched(mut self, trigger_price: impl Into<f64>) -> Self {
self.order_type = Some(OrderType::MarketIfTouched);
self.aux_price = Some(trigger_price.into());
self
}
pub fn limit_if_touched(mut self, trigger_price: impl Into<f64>, limit_price: impl Into<f64>) -> Self {
self.order_type = Some(OrderType::LimitIfTouched);
self.aux_price = Some(trigger_price.into());
self.limit_price = Some(limit_price.into());
self
}
pub fn market_to_limit(mut self) -> Self {
self.order_type = Some(OrderType::MarketToLimit);
self
}
pub fn discretionary(mut self, limit_price: impl Into<f64>, discretionary_amt: f64) -> Self {
self.order_type = Some(OrderType::Limit);
self.limit_price = Some(limit_price.into());
self.discretionary_amt = Some(discretionary_amt);
self
}
pub fn sweep_to_fill(mut self, limit_price: impl Into<f64>) -> Self {
self.order_type = Some(OrderType::Limit);
self.limit_price = Some(limit_price.into());
self.sweep_to_fill = true;
self
}
pub fn block(mut self, limit_price: impl Into<f64>) -> Self {
self.order_type = Some(OrderType::Limit);
self.limit_price = Some(limit_price.into());
self.block_order = true;
self
}
pub fn midprice(mut self, price_cap: Option<f64>) -> Self {
self.order_type = Some(OrderType::Midprice);
self.limit_price = price_cap;
self
}
pub fn relative(mut self, offset: f64, price_cap: Option<f64>) -> Self {
self.order_type = Some(OrderType::Relative);
self.aux_price = Some(offset);
self.limit_price = price_cap;
self
}
pub fn passive_relative(mut self, offset: f64) -> Self {
self.order_type = Some(OrderType::PassiveRelative);
self.aux_price = Some(offset);
self
}
pub fn at_auction(mut self, price: impl Into<f64>) -> Self {
self.order_type = Some(OrderType::AtAuction);
self.limit_price = Some(price.into());
self.time_in_force = TimeInForce::Auction;
self
}
pub fn market_on_close(mut self) -> Self {
self.order_type = Some(OrderType::MarketOnClose);
self
}
pub fn limit_on_close(mut self, limit_price: impl Into<f64>) -> Self {
self.order_type = Some(OrderType::LimitOnClose);
self.limit_price = Some(limit_price.into());
self
}
pub fn market_on_open(mut self) -> Self {
self.order_type = Some(OrderType::Market);
self.time_in_force = TimeInForce::OpeningAuction;
self
}
pub fn limit_on_open(mut self, limit_price: impl Into<f64>) -> Self {
self.order_type = Some(OrderType::Limit);
self.limit_price = Some(limit_price.into());
self.time_in_force = TimeInForce::OpeningAuction;
self
}
pub fn market_with_protection(mut self) -> Self {
self.order_type = Some(OrderType::MarketWithProtection);
self
}
pub fn stop_with_protection(mut self, stop_price: impl Into<f64>) -> Self {
self.order_type = Some(OrderType::StopWithProtection);
self.stop_price = Some(stop_price.into());
self
}
pub fn time_in_force(mut self, tif: TimeInForce) -> Self {
self.time_in_force = tif;
self
}
pub fn day_order(mut self) -> Self {
self.time_in_force = TimeInForce::Day;
self
}
pub fn good_till_cancel(mut self) -> Self {
self.time_in_force = TimeInForce::GoodTillCancel;
self
}
pub fn good_till_date(mut self, date: impl Into<String>) -> Self {
let date_str = date.into();
self.time_in_force = TimeInForce::GoodTillDate { date: date_str.clone() };
self.good_till_date = Some(date_str);
self
}
pub fn fill_or_kill(mut self) -> Self {
self.time_in_force = TimeInForce::FillOrKill;
self
}
pub fn immediate_or_cancel(mut self) -> Self {
self.time_in_force = TimeInForce::ImmediateOrCancel;
self
}
pub fn outside_rth(mut self) -> Self {
self.outside_rth = true;
self
}
pub fn regular_hours_only(mut self) -> Self {
self.outside_rth = false;
self
}
pub fn trading_hours(mut self, hours: TradingHours) -> Self {
self.outside_rth = matches!(hours, TradingHours::Extended);
self
}
pub fn hidden(mut self) -> Self {
self.hidden = true;
self
}
pub fn account(mut self, account: impl Into<String>) -> Self {
self.account = Some(account.into());
self
}
pub fn parent(mut self, parent_id: i32) -> Self {
self.parent_id = Some(parent_id);
self
}
pub fn oca_group(mut self, group: impl Into<String>, oca_type: i32) -> Self {
self.oca_group = Some(group.into());
self.oca_type = Some(oca_type);
self
}
pub fn do_not_transmit(mut self) -> Self {
self.transmit = false;
self
}
pub fn bracket(self) -> BracketOrderBuilder<'a, C> {
BracketOrderBuilder::new(self)
}
pub fn condition(mut self, condition: impl Into<OrderCondition>) -> Self {
let mut cond = condition.into();
set_conjunction(&mut cond, true);
self.conditions.push(cond);
self
}
pub fn and_condition(mut self, condition: impl Into<OrderCondition>) -> Self {
if let Some(prev) = self.conditions.last_mut() {
set_conjunction(prev, true);
}
self.conditions.push(condition.into());
self
}
pub fn or_condition(mut self, condition: impl Into<OrderCondition>) -> Self {
if let Some(prev) = self.conditions.last_mut() {
set_conjunction(prev, false);
}
self.conditions.push(condition.into());
self
}
pub fn algo(mut self, algo: impl Into<AlgoParams>) -> Self {
let params = algo.into();
self.algo_strategy = Some(params.strategy);
self.algo_params.extend(params.params);
self
}
pub fn algo_param(mut self, key: impl Into<String>, value: impl Into<String>) -> Self {
self.algo_params.push(TagValue {
tag: key.into(),
value: value.into(),
});
self
}
pub fn what_if(mut self) -> Self {
self.what_if = true;
self
}
pub fn volatility(mut self, volatility: f64) -> Self {
self.volatility = Some(volatility);
self
}
pub fn not_held(mut self) -> Self {
self.not_held = true;
self
}
pub fn all_or_none(mut self) -> Self {
self.all_or_none = true;
self
}
pub fn good_after_time(mut self, time: impl Into<String>) -> Self {
self.good_after_time = Some(time.into());
self
}
pub fn good_till_time(mut self, time: impl Into<String>) -> Self {
self.good_till_date = Some(time.into());
self
}
pub fn min_trade_qty(mut self, qty: i32) -> Self {
self.min_trade_qty = Some(qty);
self
}
pub fn min_compete_size(mut self, size: i32) -> Self {
self.min_compete_size = Some(size);
self
}
pub fn compete_against_best_offset(mut self, offset: f64) -> Self {
self.compete_against_best_offset = Some(offset);
self
}
pub fn mid_offset_at_whole(mut self, offset: f64) -> Self {
self.mid_offset_at_whole = Some(offset);
self
}
pub fn mid_offset_at_half(mut self, offset: f64) -> Self {
self.mid_offset_at_half = Some(offset);
self
}
pub fn build(self) -> Result<Order, ValidationError> {
let action = self.action.ok_or(ValidationError::MissingRequiredField("action"))?;
let quantity_raw = self.quantity.ok_or(ValidationError::MissingRequiredField("quantity"))?;
let order_type = self.order_type.ok_or(ValidationError::MissingRequiredField("order_type"))?;
let quantity = Quantity::new(quantity_raw)?;
let limit_price = if order_type.requires_limit_price() {
let price_raw = self.limit_price.ok_or(ValidationError::MissingRequiredField("limit_price"))?;
Some(Price::new(price_raw)?)
} else if let Some(price_raw) = self.limit_price {
Some(Price::new(price_raw)?)
} else {
None
};
let stop_price = match order_type {
OrderType::Stop | OrderType::StopLimit => {
let price_raw = self.stop_price.ok_or(ValidationError::MissingRequiredField("stop_price"))?;
Some(Price::new(price_raw)?)
}
_ => {
if let Some(price_raw) = self.stop_price {
Some(Price::new(price_raw)?)
} else {
None
}
}
};
let trail_stop_price = match order_type {
OrderType::TrailingStop | OrderType::TrailingStopLimit => {
if self.trailing_percent.is_none() && self.trail_stop_price.is_none() {
return Err(ValidationError::MissingRequiredField("trailing amount or stop price"));
}
if let Some(price_raw) = self.trail_stop_price {
Some(Price::new(price_raw)?)
} else {
None
}
}
_ => None,
};
if order_type == OrderType::Volatility && self.volatility.is_none() {
return Err(ValidationError::MissingRequiredField("volatility"));
}
if let TimeInForce::GoodTillDate { .. } = &self.time_in_force {
if self.good_till_date.is_none() {
return Err(ValidationError::MissingRequiredField("good_till_date"));
}
}
let mut order = Order {
action,
total_quantity: quantity.value(),
order_type: order_type.as_str().to_string(),
..Default::default()
};
if let Some(price) = limit_price {
order.limit_price = Some(price.value());
}
if let Some(price) = stop_price {
order.aux_price = Some(price.value());
}
if let Some(price) = trail_stop_price {
order.trail_stop_price = Some(price.value());
}
if let Some(percent) = self.trailing_percent {
order.trailing_percent = Some(percent);
}
if let Some(offset) = self.limit_price_offset {
order.limit_price_offset = Some(offset);
}
order.tif = crate::orders::TimeInForce::from(self.time_in_force.as_str());
if let TimeInForce::GoodTillDate { date } = &self.time_in_force {
order.good_till_date = date.clone();
}
order.outside_rth = self.outside_rth;
order.hidden = self.hidden;
order.transmit = self.transmit;
if let Some(parent_id) = self.parent_id {
order.parent_id = parent_id;
}
if let Some(group) = self.oca_group {
order.oca_group = group;
order.oca_type = self.oca_type.unwrap_or(0).into();
}
if let Some(account) = self.account {
order.account = account;
}
if let Some(time) = self.good_after_time {
order.good_after_time = time;
}
if let Some(date_time) = self.good_till_date {
if !matches!(self.time_in_force, TimeInForce::GoodTillDate { .. }) {
order.good_till_date = date_time;
}
}
if let Some(strategy) = self.algo_strategy {
order.algo_strategy = strategy;
order.algo_params = self.algo_params;
}
order.what_if = self.what_if;
if let Some(amt) = self.discretionary_amt {
order.discretionary_amt = amt;
}
if let Some(vol) = self.volatility {
order.volatility = Some(vol);
order.volatility_type = self.volatility_type.map(|v| v.into());
}
if let Some(delta) = self.delta {
order.delta = Some(delta);
}
if let Some(aux) = self.aux_price {
if order.aux_price.is_none() {
order.aux_price = Some(aux);
}
}
order.sweep_to_fill = self.sweep_to_fill;
order.block_order = self.block_order;
order.not_held = self.not_held;
order.all_or_none = self.all_or_none;
if let Some(qty) = self.min_trade_qty {
order.min_trade_qty = Some(qty);
}
if let Some(size) = self.min_compete_size {
order.min_compete_size = Some(size);
}
if let Some(offset) = self.compete_against_best_offset {
order.compete_against_best_offset = Some(offset);
}
if let Some(offset) = self.mid_offset_at_whole {
order.mid_offset_at_whole = Some(offset);
}
if let Some(offset) = self.mid_offset_at_half {
order.mid_offset_at_half = Some(offset);
}
if !self.conditions.is_empty() {
order.conditions = self.conditions;
}
if let Some(id) = self.reference_contract_id {
order.reference_contract_id = id;
}
if let Some(exchange) = self.reference_exchange {
order.reference_exchange = exchange;
}
if let Some(price) = self.stock_ref_price {
order.stock_ref_price = Some(price);
}
if let Some(lower) = self.stock_range_lower {
order.stock_range_lower = Some(lower);
}
if let Some(upper) = self.stock_range_upper {
order.stock_range_upper = Some(upper);
}
if let Some(amount) = self.reference_change_amount {
order.reference_change_amount = Some(amount);
}
if let Some(amount) = self.pegged_change_amount {
order.pegged_change_amount = Some(amount);
}
if self.is_pegged_change_amount_decrease {
order.is_pegged_change_amount_decrease = true;
}
if !self.order_combo_legs.is_empty() {
order.order_combo_legs = self.order_combo_legs;
}
if !self.smart_combo_routing_params.is_empty() {
order.smart_combo_routing_params = self.smart_combo_routing_params;
}
if let Some(qty) = self.cash_qty {
order.cash_qty = Some(qty);
}
if let Some(time) = self.manual_order_time {
order.manual_order_time = time;
}
if let Some(strategy) = self.auction_strategy {
order.auction_strategy = Some(strategy.into());
}
if let Some(price) = self.starting_price {
order.starting_price = Some(price);
}
if let Some(hedge_type) = self.hedge_type {
order.hedge_type = hedge_type;
}
Ok(order)
}
}
fn set_conjunction(condition: &mut OrderCondition, is_conjunction: bool) {
match condition {
OrderCondition::Price(c) => c.is_conjunction = is_conjunction,
OrderCondition::Time(c) => c.is_conjunction = is_conjunction,
OrderCondition::Margin(c) => c.is_conjunction = is_conjunction,
OrderCondition::Execution(c) => c.is_conjunction = is_conjunction,
OrderCondition::Volume(c) => c.is_conjunction = is_conjunction,
OrderCondition::PercentChange(c) => c.is_conjunction = is_conjunction,
}
}
#[derive(Default)]
enum BracketEntryType {
#[default]
None,
Limit(f64),
Market,
}
pub struct BracketOrderBuilder<'a, C> {
pub(crate) parent_builder: OrderBuilder<'a, C>,
entry_type: BracketEntryType,
take_profit_price: Option<f64>,
stop_loss_price: Option<f64>,
}
impl<'a, C> BracketOrderBuilder<'a, C> {
fn new(parent_builder: OrderBuilder<'a, C>) -> Self {
Self {
parent_builder,
entry_type: BracketEntryType::None,
take_profit_price: None,
stop_loss_price: None,
}
}
pub fn entry_market(mut self) -> Self {
self.entry_type = BracketEntryType::Market;
self
}
pub fn entry_limit(mut self, price: impl Into<f64>) -> Self {
self.entry_type = BracketEntryType::Limit(price.into());
self
}
pub fn take_profit(mut self, price: impl Into<f64>) -> Self {
self.take_profit_price = Some(price.into());
self
}
pub fn stop_loss(mut self, price: impl Into<f64>) -> Self {
self.stop_loss_price = Some(price.into());
self
}
pub fn build(mut self) -> Result<Vec<Order>, ValidationError> {
let take_profit_raw = self.take_profit_price.ok_or(ValidationError::MissingRequiredField("take_profit"))?;
let stop_loss_raw = self.stop_loss_price.ok_or(ValidationError::MissingRequiredField("stop_loss"))?;
let take_profit = Price::new(take_profit_raw)?;
let stop_loss = Price::new(stop_loss_raw)?;
match self.entry_type {
BracketEntryType::None => {
return Err(ValidationError::MissingRequiredField("entry (use entry_limit() or entry_market())"));
}
BracketEntryType::Limit(price) => {
let entry_price = Price::new(price)?;
validation::validate_bracket_prices(
self.parent_builder.action.as_ref(),
entry_price.value(),
take_profit.value(),
stop_loss.value(),
)?;
self.parent_builder.order_type = Some(OrderType::Limit);
self.parent_builder.limit_price = Some(entry_price.value());
}
BracketEntryType::Market => {
self.parent_builder.order_type = Some(OrderType::Market);
}
}
let mut parent = self.parent_builder.build()?;
parent.transmit = false;
let take_profit_order = Order {
action: parent.action.reverse(),
order_type: "LMT".to_string(),
total_quantity: parent.total_quantity,
limit_price: Some(take_profit.value()),
parent_id: parent.order_id,
transmit: false,
outside_rth: parent.outside_rth,
tif: parent.tif.clone(),
..Default::default()
};
let stop_loss_order = Order {
action: parent.action.reverse(),
order_type: "STP".to_string(),
total_quantity: parent.total_quantity,
aux_price: Some(stop_loss.value()),
parent_id: parent.order_id,
transmit: true,
outside_rth: parent.outside_rth,
tif: parent.tif.clone(),
..Default::default()
};
Ok(vec![parent, take_profit_order, stop_loss_order])
}
}