use std::{collections::HashMap, str::FromStr};
use nautilus_core::{UUID4, UnixNanos};
use rust_decimal_macros::dec;
use super::{
any::OrderAny, limit::LimitOrder, limit_if_touched::LimitIfTouchedOrder, market::MarketOrder,
market_if_touched::MarketIfTouchedOrder, market_to_limit::MarketToLimitOrder,
stop_limit::StopLimitOrder, stop_market::StopMarketOrder,
trailing_stop_limit::TrailingStopLimitOrder, trailing_stop_market::TrailingStopMarketOrder,
};
use crate::{
enums::{LiquiditySide, OrderSide, OrderType, TimeInForce, TrailingOffsetType, TriggerType},
events::{OrderAccepted, OrderCanceled, OrderEventAny, OrderFilled, OrderSubmitted},
identifiers::{
AccountId, ClientOrderId, InstrumentId, PositionId, StrategyId, TradeId, TraderId, Venue,
VenueOrderId,
},
instruments::{Instrument, InstrumentAny},
orders::{Order, OrderTestBuilder},
stubs::TestDefault,
types::{Money, Price, Quantity},
};
impl TestDefault for LimitOrder {
fn test_default() -> Self {
Self::new(
TraderId::test_default(),
StrategyId::test_default(),
InstrumentId::test_default(),
ClientOrderId::test_default(),
OrderSide::Buy,
Quantity::from(100_000),
Price::from("1.00000"),
TimeInForce::Gtc,
None,
false,
false,
false,
None,
None,
None,
None,
None,
None,
None,
None,
None,
None,
None,
UUID4::default(),
UnixNanos::default(),
)
}
}
impl TestDefault for LimitIfTouchedOrder {
fn test_default() -> Self {
Self::new(
TraderId::test_default(),
StrategyId::test_default(),
InstrumentId::test_default(),
ClientOrderId::test_default(),
OrderSide::Buy,
Quantity::from(100_000),
Price::from("1.00000"),
Price::from("1.00000"),
TriggerType::BidAsk,
TimeInForce::Gtc,
None,
false,
false,
false,
None,
None,
None,
None,
None,
None,
None,
None,
None,
None,
None,
UUID4::default(),
UnixNanos::default(),
)
}
}
impl TestDefault for MarketOrder {
fn test_default() -> Self {
Self::new(
TraderId::test_default(),
StrategyId::test_default(),
InstrumentId::test_default(),
ClientOrderId::test_default(),
OrderSide::Buy,
Quantity::from(100_000),
TimeInForce::Day,
UUID4::default(),
UnixNanos::default(),
false,
false,
None,
None,
None,
None,
None,
None,
None,
None,
)
}
}
impl TestDefault for MarketIfTouchedOrder {
fn test_default() -> Self {
Self::new(
TraderId::test_default(),
StrategyId::test_default(),
InstrumentId::test_default(),
ClientOrderId::test_default(),
OrderSide::Buy,
Quantity::from(100_000),
Price::from("1.00000"),
TriggerType::BidAsk,
TimeInForce::Gtc,
None,
false,
false,
None,
None,
None,
None,
None,
None,
None,
None,
None,
None,
UUID4::default(),
UnixNanos::default(),
)
}
}
impl TestDefault for MarketToLimitOrder {
fn test_default() -> Self {
Self::new(
TraderId::test_default(),
StrategyId::test_default(),
InstrumentId::test_default(),
ClientOrderId::test_default(),
OrderSide::Buy,
Quantity::from(100_000),
TimeInForce::Gtc,
None,
false,
false,
false,
None,
None,
None,
None,
None,
None,
None,
None,
None,
UUID4::default(),
UnixNanos::default(),
)
}
}
impl TestDefault for StopLimitOrder {
fn test_default() -> Self {
Self::new(
TraderId::test_default(),
StrategyId::test_default(),
InstrumentId::test_default(),
ClientOrderId::test_default(),
OrderSide::Buy,
Quantity::from(100_000),
Price::from("1.00000"),
Price::from("1.00000"),
TriggerType::BidAsk,
TimeInForce::Gtc,
None,
false,
false,
false,
None,
None,
None,
None,
None,
None,
None,
None,
None,
None,
None,
UUID4::default(),
UnixNanos::default(),
)
}
}
impl TestDefault for StopMarketOrder {
fn test_default() -> Self {
Self::new(
TraderId::test_default(),
StrategyId::test_default(),
InstrumentId::test_default(),
ClientOrderId::test_default(),
OrderSide::Buy,
Quantity::from(100_000),
Price::from("1.00000"),
TriggerType::BidAsk,
TimeInForce::Gtc,
None,
false,
false,
None,
None,
None,
None,
None,
None,
None,
None,
None,
None,
None,
UUID4::default(),
UnixNanos::default(),
)
}
}
impl TestDefault for TrailingStopLimitOrder {
fn test_default() -> Self {
Self::new(
TraderId::test_default(),
StrategyId::test_default(),
InstrumentId::test_default(),
ClientOrderId::test_default(),
OrderSide::Buy,
Quantity::from(100_000),
Price::from("1.00000"),
Price::from("1.00000"),
TriggerType::BidAsk,
dec!(0.001),
dec!(0.001),
TrailingOffsetType::Price,
TimeInForce::Gtc,
None,
false,
false,
false,
None,
None,
None,
None,
None,
None,
None,
None,
None,
None,
None,
UUID4::default(),
UnixNanos::default(),
)
}
}
impl TestDefault for TrailingStopMarketOrder {
fn test_default() -> Self {
Self::new(
TraderId::test_default(),
StrategyId::test_default(),
InstrumentId::test_default(),
ClientOrderId::test_default(),
OrderSide::Buy,
Quantity::from(100_000),
Price::from("1.00000"),
TriggerType::BidAsk,
dec!(0.001),
TrailingOffsetType::Price,
TimeInForce::Gtc,
None,
false,
false,
None,
None,
None,
None,
None,
None,
None,
None,
None,
None,
None,
UUID4::default(),
UnixNanos::default(),
)
}
}
#[derive(Debug)]
pub struct TestOrderEventStubs;
impl TestOrderEventStubs {
pub fn submitted(order: &OrderAny, account_id: AccountId) -> OrderEventAny {
let event = OrderSubmitted::new(
order.trader_id(),
order.strategy_id(),
order.instrument_id(),
order.client_order_id(),
account_id,
UUID4::new(),
UnixNanos::default(),
UnixNanos::default(),
);
OrderEventAny::Submitted(event)
}
pub fn accepted(
order: &OrderAny,
account_id: AccountId,
venue_order_id: VenueOrderId,
) -> OrderEventAny {
let event = OrderAccepted::new(
order.trader_id(),
order.strategy_id(),
order.instrument_id(),
order.client_order_id(),
venue_order_id,
account_id,
UUID4::new(),
UnixNanos::default(),
UnixNanos::default(),
false,
);
OrderEventAny::Accepted(event)
}
pub fn canceled(
order: &OrderAny,
account_id: AccountId,
venue_order_id: Option<VenueOrderId>,
) -> OrderEventAny {
let event = OrderCanceled::new(
order.trader_id(),
order.strategy_id(),
order.instrument_id(),
order.client_order_id(),
UUID4::new(),
UnixNanos::default(),
UnixNanos::default(),
false, venue_order_id,
Some(account_id),
);
OrderEventAny::Canceled(event)
}
#[allow(clippy::too_many_arguments)]
pub fn filled(
order: &OrderAny,
instrument: &InstrumentAny,
trade_id: Option<TradeId>,
position_id: Option<PositionId>,
last_px: Option<Price>,
last_qty: Option<Quantity>,
liquidity_side: Option<LiquiditySide>,
commission: Option<Money>,
ts_filled_ns: Option<UnixNanos>,
account_id: Option<AccountId>,
) -> OrderEventAny {
let venue_order_id = order
.venue_order_id()
.unwrap_or_else(VenueOrderId::test_default);
let account_id = account_id
.or(order.account_id())
.unwrap_or(AccountId::from("SIM-001"));
let trade_id = trade_id.unwrap_or(TradeId::new(
order.client_order_id().as_str().replace('O', "E").as_str(),
));
let liquidity_side = liquidity_side.unwrap_or(LiquiditySide::Maker);
let event = UUID4::new();
let position_id = position_id
.or_else(|| order.position_id())
.unwrap_or(PositionId::new("1"));
let commission = commission.unwrap_or(Money::from("2 USD"));
let last_px = last_px.unwrap_or(Price::from_str("1.0").unwrap());
let last_qty = last_qty.unwrap_or(order.quantity());
let event = OrderFilled::new(
order.trader_id(),
order.strategy_id(),
instrument.id(),
order.client_order_id(),
venue_order_id,
account_id,
trade_id,
order.order_side(),
order.order_type(),
last_qty,
last_px,
instrument.quote_currency(),
liquidity_side,
event,
ts_filled_ns.unwrap_or_default(),
UnixNanos::default(),
false,
Some(position_id),
Some(commission),
);
OrderEventAny::Filled(event)
}
}
#[derive(Debug)]
pub struct TestOrderStubs;
impl TestOrderStubs {
pub fn make_accepted_order(order: &OrderAny) -> OrderAny {
let mut new_order = order.clone();
let accepted_event = TestOrderEventStubs::accepted(
&new_order,
AccountId::from("SIM-001"),
VenueOrderId::from("V-001"),
);
new_order.apply(accepted_event).unwrap();
new_order
}
pub fn make_filled_order(
order: &OrderAny,
instrument: &InstrumentAny,
liquidity_side: LiquiditySide,
) -> OrderAny {
let mut accepted_order = Self::make_accepted_order(order);
let fill = TestOrderEventStubs::filled(
&accepted_order,
instrument,
None,
None,
None,
None,
Some(liquidity_side),
None,
None,
None,
);
accepted_order.apply(fill).unwrap();
accepted_order
}
}
#[derive(Debug)]
pub struct TestOrdersGenerator {
order_type: OrderType,
venue_instruments: HashMap<Venue, u32>,
orders_per_instrument: u32,
}
impl TestOrdersGenerator {
pub fn new(order_type: OrderType) -> Self {
Self {
order_type,
venue_instruments: HashMap::new(),
orders_per_instrument: 5,
}
}
pub fn set_orders_per_instrument(&mut self, total_orders: u32) {
self.orders_per_instrument = total_orders;
}
pub fn add_venue_and_total_instruments(&mut self, venue: Venue, total_instruments: u32) {
self.venue_instruments.insert(venue, total_instruments);
}
fn generate_order(&self, instrument_id: InstrumentId, client_order_id_index: u32) -> OrderAny {
let client_order_id =
ClientOrderId::from(format!("O-{instrument_id}-{client_order_id_index}"));
OrderTestBuilder::new(self.order_type)
.quantity(Quantity::from("1"))
.price(Price::from("1"))
.instrument_id(instrument_id)
.client_order_id(client_order_id)
.build()
}
pub fn build(&self) -> Vec<OrderAny> {
let mut orders = Vec::new();
for (venue, total_instruments) in &self.venue_instruments {
for i in 0..*total_instruments {
let instrument_id = InstrumentId::from(format!("SYMBOL-{i}.{venue}"));
for order_index in 0..self.orders_per_instrument {
let order = self.generate_order(instrument_id, order_index);
orders.push(order);
}
}
}
orders
}
}
pub fn create_order_list_sample(
total_venues: u8,
total_instruments: u32,
orders_per_instrument: u32,
) -> Vec<OrderAny> {
let mut order_generator = TestOrdersGenerator::new(OrderType::Limit);
for i in 0..total_venues {
let venue = Venue::from(format!("VENUE-{i}"));
order_generator.add_venue_and_total_instruments(venue, total_instruments);
}
order_generator.set_orders_per_instrument(orders_per_instrument);
order_generator.build()
}