use std::{cell::RefCell, rc::Rc};
use indexmap::IndexMap;
use nautilus_core::{
UUID4, UnixNanos,
correctness::{check_equal, check_slice_not_empty},
};
use nautilus_model::{
enums::{ContingencyType, OrderSide, TimeInForce, TrailingOffsetType, TriggerType},
identifiers::{
ClientOrderId, ExecAlgorithmId, InstrumentId, OrderListId, StrategyId, TraderId,
},
orders::{
LimitIfTouchedOrder, LimitOrder, MarketIfTouchedOrder, MarketOrder, Order, OrderAny,
OrderList, StopLimitOrder, StopMarketOrder, TrailingStopMarketOrder,
},
types::{Price, Quantity},
};
use rust_decimal::Decimal;
use ustr::Ustr;
use crate::{
clock::Clock,
generators::{client_order_id::ClientOrderIdGenerator, order_list_id::OrderListIdGenerator},
};
#[derive(Debug)]
pub struct OrderFactory {
clock: Rc<RefCell<dyn Clock>>,
trader_id: TraderId,
strategy_id: StrategyId,
order_id_generator: ClientOrderIdGenerator,
order_list_id_generator: OrderListIdGenerator,
}
impl OrderFactory {
pub fn new(
trader_id: TraderId,
strategy_id: StrategyId,
init_order_id_count: Option<usize>,
init_order_list_id_count: Option<usize>,
clock: Rc<RefCell<dyn Clock>>,
use_uuids_for_client_order_ids: bool,
use_hyphens_in_client_order_ids: bool,
) -> Self {
let order_id_generator = ClientOrderIdGenerator::new(
trader_id,
strategy_id,
init_order_id_count.unwrap_or(0),
clock.clone(),
use_uuids_for_client_order_ids,
use_hyphens_in_client_order_ids,
);
let order_list_id_generator = OrderListIdGenerator::new(
trader_id,
strategy_id,
init_order_list_id_count.unwrap_or(0),
clock.clone(),
);
Self {
clock,
trader_id,
strategy_id,
order_id_generator,
order_list_id_generator,
}
}
pub const fn set_client_order_id_count(&mut self, count: usize) {
self.order_id_generator.set_count(count);
}
pub const fn set_order_list_id_count(&mut self, count: usize) {
self.order_list_id_generator.set_count(count);
}
pub fn generate_client_order_id(&mut self) -> ClientOrderId {
self.order_id_generator.generate()
}
pub fn generate_order_list_id(&mut self) -> OrderListId {
self.order_list_id_generator.generate()
}
pub const fn reset_factory(&mut self) {
self.order_id_generator.reset();
self.order_list_id_generator.reset();
}
#[expect(clippy::too_many_arguments)]
pub fn market(
&mut self,
instrument_id: InstrumentId,
order_side: OrderSide,
quantity: Quantity,
time_in_force: Option<TimeInForce>,
reduce_only: Option<bool>,
quote_quantity: Option<bool>,
exec_algorithm_id: Option<ExecAlgorithmId>,
exec_algorithm_params: Option<IndexMap<Ustr, Ustr>>,
tags: Option<Vec<Ustr>>,
client_order_id: Option<ClientOrderId>,
) -> OrderAny {
let client_order_id = client_order_id.unwrap_or_else(|| self.generate_client_order_id());
let exec_spawn_id: Option<ClientOrderId> = if exec_algorithm_id.is_none() {
None
} else {
Some(client_order_id)
};
let order = MarketOrder::new(
self.trader_id,
self.strategy_id,
instrument_id,
client_order_id,
order_side,
quantity,
time_in_force.unwrap_or(TimeInForce::Gtc),
UUID4::new(),
self.clock.borrow().timestamp_ns(),
reduce_only.unwrap_or(false),
quote_quantity.unwrap_or(false),
Some(ContingencyType::NoContingency),
None,
None,
None,
exec_algorithm_id,
exec_algorithm_params,
exec_spawn_id,
tags,
);
OrderAny::Market(order)
}
#[expect(clippy::too_many_arguments)]
pub fn limit(
&mut self,
instrument_id: InstrumentId,
order_side: OrderSide,
quantity: Quantity,
price: Price,
time_in_force: Option<TimeInForce>,
expire_time: Option<nautilus_core::UnixNanos>,
post_only: Option<bool>,
reduce_only: Option<bool>,
quote_quantity: Option<bool>,
display_qty: Option<Quantity>,
emulation_trigger: Option<TriggerType>,
trigger_instrument_id: Option<InstrumentId>,
exec_algorithm_id: Option<ExecAlgorithmId>,
exec_algorithm_params: Option<IndexMap<Ustr, Ustr>>,
tags: Option<Vec<Ustr>>,
client_order_id: Option<ClientOrderId>,
) -> OrderAny {
let client_order_id = client_order_id.unwrap_or_else(|| self.generate_client_order_id());
let exec_spawn_id: Option<ClientOrderId> = if exec_algorithm_id.is_none() {
None
} else {
Some(client_order_id)
};
let order = LimitOrder::new(
self.trader_id,
self.strategy_id,
instrument_id,
client_order_id,
order_side,
quantity,
price,
time_in_force.unwrap_or(TimeInForce::Gtc),
expire_time,
post_only.unwrap_or(false),
reduce_only.unwrap_or(false),
quote_quantity.unwrap_or(false),
display_qty,
emulation_trigger,
trigger_instrument_id,
Some(ContingencyType::NoContingency),
None,
None,
None,
exec_algorithm_id,
exec_algorithm_params,
exec_spawn_id,
tags,
UUID4::new(),
self.clock.borrow().timestamp_ns(),
);
OrderAny::Limit(order)
}
#[expect(clippy::too_many_arguments)]
pub fn stop_market(
&mut self,
instrument_id: InstrumentId,
order_side: OrderSide,
quantity: Quantity,
trigger_price: Price,
trigger_type: Option<TriggerType>,
time_in_force: Option<TimeInForce>,
expire_time: Option<nautilus_core::UnixNanos>,
reduce_only: Option<bool>,
quote_quantity: Option<bool>,
display_qty: Option<Quantity>,
emulation_trigger: Option<TriggerType>,
trigger_instrument_id: Option<InstrumentId>,
exec_algorithm_id: Option<ExecAlgorithmId>,
exec_algorithm_params: Option<IndexMap<Ustr, Ustr>>,
tags: Option<Vec<Ustr>>,
client_order_id: Option<ClientOrderId>,
) -> OrderAny {
let client_order_id = client_order_id.unwrap_or_else(|| self.generate_client_order_id());
let exec_spawn_id: Option<ClientOrderId> = if exec_algorithm_id.is_none() {
None
} else {
Some(client_order_id)
};
let order = StopMarketOrder::new(
self.trader_id,
self.strategy_id,
instrument_id,
client_order_id,
order_side,
quantity,
trigger_price,
trigger_type.unwrap_or(TriggerType::Default),
time_in_force.unwrap_or(TimeInForce::Gtc),
expire_time,
reduce_only.unwrap_or(false),
quote_quantity.unwrap_or(false),
display_qty,
emulation_trigger,
trigger_instrument_id,
Some(ContingencyType::NoContingency),
None,
None,
None,
exec_algorithm_id,
exec_algorithm_params,
exec_spawn_id,
tags,
UUID4::new(),
self.clock.borrow().timestamp_ns(),
);
OrderAny::StopMarket(order)
}
#[expect(clippy::too_many_arguments)]
pub fn stop_limit(
&mut self,
instrument_id: InstrumentId,
order_side: OrderSide,
quantity: Quantity,
price: Price,
trigger_price: Price,
trigger_type: Option<TriggerType>,
time_in_force: Option<TimeInForce>,
expire_time: Option<nautilus_core::UnixNanos>,
post_only: Option<bool>,
reduce_only: Option<bool>,
quote_quantity: Option<bool>,
display_qty: Option<Quantity>,
emulation_trigger: Option<TriggerType>,
trigger_instrument_id: Option<InstrumentId>,
exec_algorithm_id: Option<ExecAlgorithmId>,
exec_algorithm_params: Option<IndexMap<Ustr, Ustr>>,
tags: Option<Vec<Ustr>>,
client_order_id: Option<ClientOrderId>,
) -> OrderAny {
let client_order_id = client_order_id.unwrap_or_else(|| self.generate_client_order_id());
let exec_spawn_id: Option<ClientOrderId> = if exec_algorithm_id.is_none() {
None
} else {
Some(client_order_id)
};
let order = StopLimitOrder::new(
self.trader_id,
self.strategy_id,
instrument_id,
client_order_id,
order_side,
quantity,
price,
trigger_price,
trigger_type.unwrap_or(TriggerType::Default),
time_in_force.unwrap_or(TimeInForce::Gtc),
expire_time,
post_only.unwrap_or(false),
reduce_only.unwrap_or(false),
quote_quantity.unwrap_or(false),
display_qty,
emulation_trigger,
trigger_instrument_id,
Some(ContingencyType::NoContingency),
None,
None,
None,
exec_algorithm_id,
exec_algorithm_params,
exec_spawn_id,
tags,
UUID4::new(),
self.clock.borrow().timestamp_ns(),
);
OrderAny::StopLimit(order)
}
#[expect(clippy::too_many_arguments)]
pub fn market_if_touched(
&mut self,
instrument_id: InstrumentId,
order_side: OrderSide,
quantity: Quantity,
trigger_price: Price,
trigger_type: Option<TriggerType>,
time_in_force: Option<TimeInForce>,
expire_time: Option<nautilus_core::UnixNanos>,
reduce_only: Option<bool>,
quote_quantity: Option<bool>,
emulation_trigger: Option<TriggerType>,
trigger_instrument_id: Option<InstrumentId>,
exec_algorithm_id: Option<ExecAlgorithmId>,
exec_algorithm_params: Option<IndexMap<Ustr, Ustr>>,
tags: Option<Vec<Ustr>>,
client_order_id: Option<ClientOrderId>,
) -> OrderAny {
let client_order_id = client_order_id.unwrap_or_else(|| self.generate_client_order_id());
let exec_spawn_id: Option<ClientOrderId> = if exec_algorithm_id.is_none() {
None
} else {
Some(client_order_id)
};
let order = MarketIfTouchedOrder::new(
self.trader_id,
self.strategy_id,
instrument_id,
client_order_id,
order_side,
quantity,
trigger_price,
trigger_type.unwrap_or(TriggerType::Default),
time_in_force.unwrap_or(TimeInForce::Gtc),
expire_time,
reduce_only.unwrap_or(false),
quote_quantity.unwrap_or(false),
emulation_trigger,
trigger_instrument_id,
Some(ContingencyType::NoContingency),
None,
None,
None,
exec_algorithm_id,
exec_algorithm_params,
exec_spawn_id,
tags,
UUID4::new(),
self.clock.borrow().timestamp_ns(),
);
OrderAny::MarketIfTouched(order)
}
#[expect(clippy::too_many_arguments)]
pub fn limit_if_touched(
&mut self,
instrument_id: InstrumentId,
order_side: OrderSide,
quantity: Quantity,
price: Price,
trigger_price: Price,
trigger_type: Option<TriggerType>,
time_in_force: Option<TimeInForce>,
expire_time: Option<nautilus_core::UnixNanos>,
post_only: Option<bool>,
reduce_only: Option<bool>,
quote_quantity: Option<bool>,
display_qty: Option<Quantity>,
emulation_trigger: Option<TriggerType>,
trigger_instrument_id: Option<InstrumentId>,
exec_algorithm_id: Option<ExecAlgorithmId>,
exec_algorithm_params: Option<IndexMap<Ustr, Ustr>>,
tags: Option<Vec<Ustr>>,
client_order_id: Option<ClientOrderId>,
) -> OrderAny {
let client_order_id = client_order_id.unwrap_or_else(|| self.generate_client_order_id());
let exec_spawn_id: Option<ClientOrderId> = if exec_algorithm_id.is_none() {
None
} else {
Some(client_order_id)
};
let order = LimitIfTouchedOrder::new(
self.trader_id,
self.strategy_id,
instrument_id,
client_order_id,
order_side,
quantity,
price,
trigger_price,
trigger_type.unwrap_or(TriggerType::Default),
time_in_force.unwrap_or(TimeInForce::Gtc),
expire_time,
post_only.unwrap_or(false),
reduce_only.unwrap_or(false),
quote_quantity.unwrap_or(false),
display_qty,
emulation_trigger,
trigger_instrument_id,
Some(ContingencyType::NoContingency),
None,
None,
None,
exec_algorithm_id,
exec_algorithm_params,
exec_spawn_id,
tags,
UUID4::new(),
self.clock.borrow().timestamp_ns(),
);
OrderAny::LimitIfTouched(order)
}
#[expect(clippy::too_many_arguments)]
pub fn trailing_stop_market(
&mut self,
instrument_id: InstrumentId,
order_side: OrderSide,
quantity: Quantity,
trailing_offset: Decimal,
trailing_offset_type: Option<TrailingOffsetType>,
activation_price: Option<Price>,
trigger_price: Option<Price>,
trigger_type: Option<TriggerType>,
time_in_force: Option<TimeInForce>,
expire_time: Option<nautilus_core::UnixNanos>,
reduce_only: Option<bool>,
quote_quantity: Option<bool>,
display_qty: Option<Quantity>,
emulation_trigger: Option<TriggerType>,
trigger_instrument_id: Option<InstrumentId>,
exec_algorithm_id: Option<ExecAlgorithmId>,
exec_algorithm_params: Option<IndexMap<Ustr, Ustr>>,
tags: Option<Vec<Ustr>>,
client_order_id: Option<ClientOrderId>,
) -> OrderAny {
let client_order_id = client_order_id.unwrap_or_else(|| self.generate_client_order_id());
let exec_spawn_id: Option<ClientOrderId> = if exec_algorithm_id.is_none() {
None
} else {
Some(client_order_id)
};
let trigger_price = trigger_price
.or(activation_price)
.expect("TrailingStopMarket requires either trigger_price or activation_price");
let order = TrailingStopMarketOrder::new(
self.trader_id,
self.strategy_id,
instrument_id,
client_order_id,
order_side,
quantity,
trigger_price,
trigger_type.unwrap_or(TriggerType::Default),
trailing_offset,
trailing_offset_type.unwrap_or(TrailingOffsetType::Price),
time_in_force.unwrap_or(TimeInForce::Gtc),
expire_time,
reduce_only.unwrap_or(false),
quote_quantity.unwrap_or(false),
display_qty,
emulation_trigger,
trigger_instrument_id,
Some(ContingencyType::NoContingency),
None,
None,
None,
exec_algorithm_id,
exec_algorithm_params,
exec_spawn_id,
tags,
UUID4::new(),
self.clock.borrow().timestamp_ns(),
);
let mut order = OrderAny::TrailingStopMarket(order);
if let (Some(activation_price), OrderAny::TrailingStopMarket(tsm)) =
(activation_price, &mut order)
{
tsm.activation_price = Some(activation_price);
}
order
}
pub fn create_list(&mut self, orders: &mut [OrderAny], ts_init: UnixNanos) -> OrderList {
check_slice_not_empty(orders, stringify!(orders)).unwrap();
let instrument_id = orders[0].instrument_id();
for order in orders.iter().skip(1) {
check_equal(
&order.instrument_id(),
&instrument_id,
"instrument_id",
"first order instrument_id",
)
.unwrap();
check_equal(
&order.strategy_id(),
&self.strategy_id,
"strategy_id",
"factory strategy_id",
)
.unwrap();
}
let order_list_id = self.generate_order_list_id();
let order_ids: Vec<ClientOrderId> = orders.iter().map(|o| o.client_order_id()).collect();
for order in orders.iter_mut() {
order.set_order_list_id(order_list_id);
}
OrderList::new(
order_list_id,
instrument_id,
self.strategy_id,
order_ids,
ts_init,
)
}
#[expect(clippy::too_many_arguments)]
pub fn bracket(
&mut self,
instrument_id: InstrumentId,
order_side: OrderSide,
quantity: Quantity,
entry_price: Option<Price>,
sl_trigger_price: Price,
sl_trigger_type: Option<TriggerType>,
tp_price: Price,
entry_trigger_price: Option<Price>,
time_in_force: Option<TimeInForce>,
expire_time: Option<nautilus_core::UnixNanos>,
sl_time_in_force: Option<TimeInForce>,
post_only: Option<bool>,
reduce_only: Option<bool>,
quote_quantity: Option<bool>,
emulation_trigger: Option<TriggerType>,
trigger_instrument_id: Option<InstrumentId>,
exec_algorithm_id: Option<ExecAlgorithmId>,
exec_algorithm_params: Option<IndexMap<Ustr, Ustr>>,
tags: Option<Vec<Ustr>>,
) -> Vec<OrderAny> {
let order_list_id = self.generate_order_list_id();
let ts_init = self.clock.borrow().timestamp_ns();
let entry_client_order_id = self.generate_client_order_id();
let sl_client_order_id = self.generate_client_order_id();
let tp_client_order_id = self.generate_client_order_id();
let entry_exec_spawn_id = exec_algorithm_id.as_ref().map(|_| entry_client_order_id);
let sl_exec_spawn_id = exec_algorithm_id.as_ref().map(|_| sl_client_order_id);
let tp_exec_spawn_id = exec_algorithm_id.as_ref().map(|_| tp_client_order_id);
let entry_contingency_type = Some(ContingencyType::Oto);
let entry_order_list_id = Some(order_list_id);
let entry_linked_order_ids = Some(vec![sl_client_order_id, tp_client_order_id]);
let entry_parent_order_id = None;
let entry_order = if let Some(trigger_price) = entry_trigger_price {
if let Some(price) = entry_price {
OrderAny::StopLimit(StopLimitOrder::new(
self.trader_id,
self.strategy_id,
instrument_id,
entry_client_order_id,
order_side,
quantity,
price,
trigger_price,
TriggerType::Default,
time_in_force.unwrap_or(TimeInForce::Gtc),
expire_time,
post_only.unwrap_or(false),
reduce_only.unwrap_or(false),
quote_quantity.unwrap_or(false),
None, emulation_trigger,
trigger_instrument_id,
entry_contingency_type,
entry_order_list_id,
entry_linked_order_ids,
entry_parent_order_id,
exec_algorithm_id,
exec_algorithm_params.clone(),
entry_exec_spawn_id,
tags.clone(),
UUID4::new(),
ts_init,
))
} else {
OrderAny::StopMarket(StopMarketOrder::new(
self.trader_id,
self.strategy_id,
instrument_id,
entry_client_order_id,
order_side,
quantity,
trigger_price,
TriggerType::Default,
time_in_force.unwrap_or(TimeInForce::Gtc),
expire_time,
reduce_only.unwrap_or(false),
quote_quantity.unwrap_or(false),
None, emulation_trigger,
trigger_instrument_id,
entry_contingency_type,
entry_order_list_id,
entry_linked_order_ids,
entry_parent_order_id,
exec_algorithm_id,
exec_algorithm_params.clone(),
entry_exec_spawn_id,
tags.clone(),
UUID4::new(),
ts_init,
))
}
} else if let Some(price) = entry_price {
OrderAny::Limit(LimitOrder::new(
self.trader_id,
self.strategy_id,
instrument_id,
entry_client_order_id,
order_side,
quantity,
price,
time_in_force.unwrap_or(TimeInForce::Gtc),
expire_time,
post_only.unwrap_or(false),
reduce_only.unwrap_or(false),
quote_quantity.unwrap_or(false),
None, emulation_trigger,
trigger_instrument_id,
entry_contingency_type,
entry_order_list_id,
entry_linked_order_ids,
entry_parent_order_id,
exec_algorithm_id,
exec_algorithm_params.clone(),
entry_exec_spawn_id,
tags.clone(),
UUID4::new(),
ts_init,
))
} else {
OrderAny::Market(MarketOrder::new(
self.trader_id,
self.strategy_id,
instrument_id,
entry_client_order_id,
order_side,
quantity,
time_in_force.unwrap_or(TimeInForce::Gtc),
UUID4::new(),
ts_init,
reduce_only.unwrap_or(false),
quote_quantity.unwrap_or(false),
entry_contingency_type,
entry_order_list_id,
entry_linked_order_ids,
entry_parent_order_id,
exec_algorithm_id,
exec_algorithm_params.clone(),
entry_exec_spawn_id,
tags.clone(),
))
};
let sl_tp_side = match order_side {
OrderSide::Buy => OrderSide::Sell,
OrderSide::Sell => OrderSide::Buy,
OrderSide::NoOrderSide => OrderSide::NoOrderSide,
};
let sl_contingency_type = Some(ContingencyType::Oco);
let sl_order_list_id = Some(order_list_id);
let sl_linked_order_ids = Some(vec![tp_client_order_id]);
let sl_parent_order_id = Some(entry_client_order_id);
let sl_order = OrderAny::StopMarket(StopMarketOrder::new(
self.trader_id,
self.strategy_id,
instrument_id,
sl_client_order_id,
sl_tp_side,
quantity,
sl_trigger_price,
sl_trigger_type.unwrap_or(TriggerType::Default),
sl_time_in_force.unwrap_or(TimeInForce::Gtc),
None, true, quote_quantity.unwrap_or(false),
None, emulation_trigger,
trigger_instrument_id,
sl_contingency_type,
sl_order_list_id,
sl_linked_order_ids,
sl_parent_order_id,
exec_algorithm_id,
exec_algorithm_params.clone(),
sl_exec_spawn_id,
tags.clone(),
UUID4::new(),
ts_init,
));
let tp_contingency_type = Some(ContingencyType::Oco);
let tp_order_list_id = Some(order_list_id);
let tp_linked_order_ids = Some(vec![sl_client_order_id]);
let tp_parent_order_id = Some(entry_client_order_id);
let tp_order = OrderAny::Limit(LimitOrder::new(
self.trader_id,
self.strategy_id,
instrument_id,
tp_client_order_id,
sl_tp_side,
quantity,
tp_price,
time_in_force.unwrap_or(TimeInForce::Gtc),
expire_time,
post_only.unwrap_or(false),
true, quote_quantity.unwrap_or(false),
None, emulation_trigger,
trigger_instrument_id,
tp_contingency_type,
tp_order_list_id,
tp_linked_order_ids,
tp_parent_order_id,
exec_algorithm_id,
exec_algorithm_params,
tp_exec_spawn_id,
tags,
UUID4::new(),
ts_init,
));
vec![entry_order, sl_order, tp_order]
}
}
#[cfg(test)]
pub mod tests {
use std::{cell::RefCell, rc::Rc};
use nautilus_core::UnixNanos;
use nautilus_model::{
enums::{ContingencyType, OrderSide, TimeInForce, TriggerType},
identifiers::{
ClientOrderId, InstrumentId, OrderListId,
stubs::{strategy_id_ema_cross, trader_id},
},
orders::Order,
types::Price,
};
use rstest::{fixture, rstest};
use crate::{clock::TestClock, factories::OrderFactory};
#[fixture]
pub fn order_factory() -> OrderFactory {
let trader_id = trader_id();
let strategy_id = strategy_id_ema_cross();
let clock = Rc::new(RefCell::new(TestClock::new()));
OrderFactory::new(
trader_id,
strategy_id,
None,
None,
clock,
false, true, )
}
#[rstest]
fn test_generate_client_order_id(mut order_factory: OrderFactory) {
let client_order_id = order_factory.generate_client_order_id();
assert_eq!(
client_order_id,
ClientOrderId::new("O-19700101-000000-001-001-1")
);
}
#[rstest]
fn test_generate_order_list_id(mut order_factory: OrderFactory) {
let order_list_id = order_factory.generate_order_list_id();
assert_eq!(
order_list_id,
OrderListId::new("OL-19700101-000000-001-001-1")
);
}
#[rstest]
fn test_set_client_order_id_count(mut order_factory: OrderFactory) {
order_factory.set_client_order_id_count(10);
let client_order_id = order_factory.generate_client_order_id();
assert_eq!(
client_order_id,
ClientOrderId::new("O-19700101-000000-001-001-11")
);
}
#[rstest]
fn test_set_order_list_id_count(mut order_factory: OrderFactory) {
order_factory.set_order_list_id_count(10);
let order_list_id = order_factory.generate_order_list_id();
assert_eq!(
order_list_id,
OrderListId::new("OL-19700101-000000-001-001-11")
);
}
#[rstest]
fn test_reset_factory(mut order_factory: OrderFactory) {
order_factory.generate_order_list_id();
order_factory.generate_client_order_id();
order_factory.reset_factory();
let client_order_id = order_factory.generate_client_order_id();
let order_list_id = order_factory.generate_order_list_id();
assert_eq!(
client_order_id,
ClientOrderId::new("O-19700101-000000-001-001-1")
);
assert_eq!(
order_list_id,
OrderListId::new("OL-19700101-000000-001-001-1")
);
}
#[fixture]
pub fn order_factory_with_uuids() -> OrderFactory {
let trader_id = trader_id();
let strategy_id = strategy_id_ema_cross();
let clock = Rc::new(RefCell::new(TestClock::new()));
OrderFactory::new(
trader_id,
strategy_id,
None,
None,
clock,
true, true, )
}
#[fixture]
pub fn order_factory_with_hyphens_removed() -> OrderFactory {
let trader_id = trader_id();
let strategy_id = strategy_id_ema_cross();
let clock = Rc::new(RefCell::new(TestClock::new()));
OrderFactory::new(
trader_id,
strategy_id,
None,
None,
clock,
false, false, )
}
#[fixture]
pub fn order_factory_with_uuids_and_hyphens_removed() -> OrderFactory {
let trader_id = trader_id();
let strategy_id = strategy_id_ema_cross();
let clock = Rc::new(RefCell::new(TestClock::new()));
OrderFactory::new(
trader_id,
strategy_id,
None,
None,
clock,
true, false, )
}
#[rstest]
fn test_generate_client_order_id_with_uuids(mut order_factory_with_uuids: OrderFactory) {
let client_order_id = order_factory_with_uuids.generate_client_order_id();
assert_eq!(client_order_id.as_str().len(), 36);
assert!(client_order_id.as_str().contains('-'));
}
#[rstest]
fn test_generate_client_order_id_with_hyphens_removed(
mut order_factory_with_hyphens_removed: OrderFactory,
) {
let client_order_id = order_factory_with_hyphens_removed.generate_client_order_id();
assert_eq!(
client_order_id,
ClientOrderId::new("O197001010000000010011")
);
assert!(!client_order_id.as_str().contains('-'));
}
#[rstest]
fn test_generate_client_order_id_with_uuids_and_hyphens_removed(
mut order_factory_with_uuids_and_hyphens_removed: OrderFactory,
) {
let client_order_id =
order_factory_with_uuids_and_hyphens_removed.generate_client_order_id();
assert_eq!(client_order_id.as_str().len(), 32);
assert!(!client_order_id.as_str().contains('-'));
}
#[rstest]
fn test_market_order(mut order_factory: OrderFactory) {
let market_order = order_factory.market(
InstrumentId::from("BTCUSDT.BINANCE"),
OrderSide::Buy,
100.into(),
Some(TimeInForce::Gtc),
Some(false),
Some(false),
None,
None,
None,
None,
);
assert_eq!(market_order.instrument_id(), "BTCUSDT.BINANCE".into());
assert_eq!(market_order.order_side(), OrderSide::Buy);
assert_eq!(market_order.quantity(), 100.into());
assert_eq!(market_order.exec_algorithm_id(), None);
assert_eq!(
market_order.client_order_id(),
ClientOrderId::new("O-19700101-000000-001-001-1")
);
}
#[rstest]
fn test_limit_order(mut order_factory: OrderFactory) {
let limit_order = order_factory.limit(
InstrumentId::from("BTCUSDT.BINANCE"),
OrderSide::Buy,
100.into(),
Price::from("50000.00"),
Some(TimeInForce::Gtc),
None,
Some(false),
Some(false),
Some(false),
None,
None,
None,
None,
None,
None,
None,
);
assert_eq!(limit_order.instrument_id(), "BTCUSDT.BINANCE".into());
assert_eq!(limit_order.order_side(), OrderSide::Buy);
assert_eq!(limit_order.quantity(), 100.into());
assert_eq!(limit_order.price(), Some(Price::from("50000.00")));
assert_eq!(
limit_order.client_order_id(),
ClientOrderId::new("O-19700101-000000-001-001-1")
);
}
#[rstest]
fn test_limit_order_with_post_only(mut order_factory: OrderFactory) {
let limit_order = order_factory.limit(
InstrumentId::from("BTCUSDT.BINANCE"),
OrderSide::Buy,
100.into(),
Price::from("50000.00"),
Some(TimeInForce::Gtc),
None,
Some(true), Some(false),
Some(false),
None,
None,
None,
None,
None,
None,
None,
);
assert!(limit_order.is_post_only());
}
#[rstest]
fn test_limit_order_with_display_qty(mut order_factory: OrderFactory) {
let limit_order = order_factory.limit(
InstrumentId::from("BTCUSDT.BINANCE"),
OrderSide::Buy,
100.into(),
Price::from("50000.00"),
Some(TimeInForce::Gtc),
None,
Some(false), Some(false), Some(false), Some(50.into()), None,
None,
None,
None,
None,
None,
);
assert_eq!(limit_order.display_qty(), Some(50.into()));
}
#[rstest]
fn test_stop_market_order(mut order_factory: OrderFactory) {
let stop_order = order_factory.stop_market(
InstrumentId::from("BTCUSDT.BINANCE"),
OrderSide::Sell,
100.into(),
Price::from("45000.00"),
Some(TriggerType::LastPrice),
Some(TimeInForce::Gtc),
None,
Some(false),
Some(false),
None,
None,
None,
None,
None,
None,
None,
);
assert_eq!(stop_order.instrument_id(), "BTCUSDT.BINANCE".into());
assert_eq!(stop_order.order_side(), OrderSide::Sell);
assert_eq!(stop_order.quantity(), 100.into());
assert_eq!(stop_order.trigger_price(), Some(Price::from("45000.00")));
assert_eq!(stop_order.trigger_type(), Some(TriggerType::LastPrice));
}
#[rstest]
fn test_stop_limit_order(mut order_factory: OrderFactory) {
let stop_limit_order = order_factory.stop_limit(
InstrumentId::from("BTCUSDT.BINANCE"),
OrderSide::Sell,
100.into(),
Price::from("45100.00"), Price::from("45000.00"), Some(TriggerType::LastPrice),
Some(TimeInForce::Gtc),
None,
Some(false),
Some(false),
Some(false),
None,
None,
None,
None,
None,
None,
None,
);
assert_eq!(stop_limit_order.instrument_id(), "BTCUSDT.BINANCE".into());
assert_eq!(stop_limit_order.order_side(), OrderSide::Sell);
assert_eq!(stop_limit_order.quantity(), 100.into());
assert_eq!(stop_limit_order.price(), Some(Price::from("45100.00")));
assert_eq!(
stop_limit_order.trigger_price(),
Some(Price::from("45000.00"))
);
assert_eq!(
stop_limit_order.trigger_type(),
Some(TriggerType::LastPrice)
);
}
#[rstest]
fn test_market_if_touched_order(mut order_factory: OrderFactory) {
let mit_order = order_factory.market_if_touched(
InstrumentId::from("BTCUSDT.BINANCE"),
OrderSide::Buy,
100.into(),
Price::from("48000.00"),
Some(TriggerType::LastPrice),
Some(TimeInForce::Gtc),
None,
Some(false),
Some(false),
None,
None,
None,
None,
None,
None,
);
assert_eq!(mit_order.instrument_id(), "BTCUSDT.BINANCE".into());
assert_eq!(mit_order.order_side(), OrderSide::Buy);
assert_eq!(mit_order.quantity(), 100.into());
assert_eq!(mit_order.trigger_price(), Some(Price::from("48000.00")));
assert_eq!(mit_order.trigger_type(), Some(TriggerType::LastPrice));
}
#[rstest]
fn test_limit_if_touched_order(mut order_factory: OrderFactory) {
let lit_order = order_factory.limit_if_touched(
InstrumentId::from("BTCUSDT.BINANCE"),
OrderSide::Buy,
100.into(),
Price::from("48100.00"), Price::from("48000.00"), Some(TriggerType::LastPrice),
Some(TimeInForce::Gtc),
None,
Some(false),
Some(false),
Some(false),
None,
None,
None,
None,
None,
None,
None,
);
assert_eq!(lit_order.instrument_id(), "BTCUSDT.BINANCE".into());
assert_eq!(lit_order.order_side(), OrderSide::Buy);
assert_eq!(lit_order.quantity(), 100.into());
assert_eq!(lit_order.price(), Some(Price::from("48100.00")));
assert_eq!(lit_order.trigger_price(), Some(Price::from("48000.00")));
assert_eq!(lit_order.trigger_type(), Some(TriggerType::LastPrice));
}
#[rstest]
fn test_bracket_order_with_market_entry(mut order_factory: OrderFactory) {
let orders = order_factory.bracket(
InstrumentId::from("BTCUSDT.BINANCE"),
OrderSide::Buy,
100.into(),
None, Price::from("45000.00"), None, Price::from("55000.00"), None, Some(TimeInForce::Gtc),
None,
None, Some(false),
Some(false),
Some(false),
None,
None,
None,
None,
None,
);
assert_eq!(orders.len(), 3);
assert_eq!(orders[0].instrument_id(), "BTCUSDT.BINANCE".into());
assert_eq!(orders[0].order_side(), OrderSide::Buy);
assert_eq!(orders[1].order_side(), OrderSide::Sell);
assert_eq!(orders[1].trigger_price(), Some(Price::from("45000.00")));
assert_eq!(orders[2].order_side(), OrderSide::Sell);
assert_eq!(orders[2].price(), Some(Price::from("55000.00")));
}
#[rstest]
fn test_bracket_order_with_limit_entry(mut order_factory: OrderFactory) {
let orders = order_factory.bracket(
InstrumentId::from("BTCUSDT.BINANCE"),
OrderSide::Buy,
100.into(),
Some(Price::from("49000.00")), Price::from("45000.00"), None, Price::from("55000.00"), None, Some(TimeInForce::Gtc),
None,
None, Some(false),
Some(false),
Some(false),
None,
None,
None,
None,
None,
);
assert_eq!(orders.len(), 3);
assert_eq!(orders[0].price(), Some(Price::from("49000.00")));
}
#[rstest]
fn test_bracket_order_with_stop_entry(mut order_factory: OrderFactory) {
let orders = order_factory.bracket(
InstrumentId::from("BTCUSDT.BINANCE"),
OrderSide::Buy,
100.into(),
None, Price::from("45000.00"), None, Price::from("55000.00"), Some(Price::from("51000.00")), Some(TimeInForce::Gtc),
None,
None, Some(false),
Some(false),
Some(false),
None,
None,
None,
None,
None,
);
assert_eq!(orders.len(), 3);
assert_eq!(orders[0].trigger_price(), Some(Price::from("51000.00")));
}
#[rstest]
fn test_bracket_order_sell_side(mut order_factory: OrderFactory) {
let orders = order_factory.bracket(
InstrumentId::from("BTCUSDT.BINANCE"),
OrderSide::Sell,
100.into(),
Some(Price::from("51000.00")), Price::from("55000.00"), None, Price::from("45000.00"), None,
Some(TimeInForce::Gtc),
None,
None, Some(false),
Some(false),
Some(false),
None,
None,
None,
None,
None,
);
assert_eq!(orders.len(), 3);
assert_eq!(orders[0].order_side(), OrderSide::Sell);
assert_eq!(orders[1].order_side(), OrderSide::Buy);
assert_eq!(orders[2].order_side(), OrderSide::Buy);
}
#[rstest]
fn test_bracket_order_sets_contingencies(mut order_factory: OrderFactory) {
let orders = order_factory.bracket(
InstrumentId::from("BTCUSDT.BINANCE"),
OrderSide::Buy,
100.into(),
Some(Price::from("50000.00")), Price::from("45000.00"), None, Price::from("55000.00"), None, Some(TimeInForce::Gtc),
None,
None, Some(false),
Some(false),
Some(false),
None,
None,
None,
None,
None,
);
let entry = &orders[0];
let stop = &orders[1];
let take = &orders[2];
let order_list_id = entry
.order_list_id()
.expect("Entry should have order_list_id");
assert_eq!(entry.contingency_type(), Some(ContingencyType::Oto));
assert_eq!(
entry.linked_order_ids().unwrap(),
&[stop.client_order_id(), take.client_order_id()]
);
assert_eq!(stop.order_list_id(), Some(order_list_id));
assert_eq!(stop.contingency_type(), Some(ContingencyType::Oco));
assert_eq!(stop.parent_order_id(), Some(entry.client_order_id()));
assert_eq!(stop.linked_order_ids().unwrap(), &[take.client_order_id()]);
assert_eq!(take.order_list_id(), Some(order_list_id));
assert_eq!(take.contingency_type(), Some(ContingencyType::Oco));
assert_eq!(take.parent_order_id(), Some(entry.client_order_id()));
assert_eq!(take.linked_order_ids().unwrap(), &[stop.client_order_id()]);
}
#[rstest]
fn test_create_list_from_plain_orders(mut order_factory: OrderFactory) {
let entry = order_factory.limit(
InstrumentId::from("BTCUSDT.BINANCE"),
OrderSide::Buy,
100.into(),
Price::from("50000.00"),
None,
None,
None,
None,
None,
None,
None,
None,
None,
None,
None,
None,
);
let sl = order_factory.stop_market(
InstrumentId::from("BTCUSDT.BINANCE"),
OrderSide::Sell,
100.into(),
Price::from("45000.00"),
None,
None,
None,
None,
None,
None,
None,
None,
None,
None,
None,
None,
);
let mut orders = vec![entry.clone(), sl.clone()];
let order_list = order_factory.create_list(&mut orders, UnixNanos::default());
assert_eq!(order_list.len(), 2);
assert_eq!(
order_list.instrument_id,
InstrumentId::from("BTCUSDT.BINANCE")
);
assert_eq!(order_list.client_order_ids[0], entry.client_order_id());
assert_eq!(order_list.client_order_ids[1], sl.client_order_id());
assert_eq!(
order_list.id,
OrderListId::new("OL-19700101-000000-001-001-1"),
);
assert_eq!(orders[0].order_list_id(), Some(order_list.id));
assert_eq!(orders[1].order_list_id(), Some(order_list.id));
}
}