use crate::contracts::{Contract, Currency, Exchange, Symbol};
use crate::errors::Error;
use crate::orders::builder::tests::async_mock_client::mock::AsyncMockClient;
use crate::orders::builder::{BracketOrderBuilder, BracketOrderIds, OrderBuilder, OrderId};
use crate::orders::{Action, Order, OrderData, OrderState, OrderStatus, OrderUpdate, PlaceOrder};
use futures::{Stream, StreamExt};
use std::pin::Pin;
fn create_stock_contract(symbol: &str) -> Contract {
Contract {
symbol: Symbol::from(symbol),
security_type: crate::contracts::SecurityType::Stock,
exchange: Exchange::from("SMART"),
currency: Currency::from("USD"),
..Default::default()
}
}
mod orders {
use super::*;
use futures::Stream;
use std::pin::Pin;
pub async fn submit_order(client: &AsyncMockClient, order_id: i32, contract: &Contract, order: &Order) -> Result<(), Error> {
client.submit_order(order_id, contract, order).await
}
pub async fn place_order(
client: &AsyncMockClient,
order_id: i32,
contract: &Contract,
order: &Order,
) -> Result<Pin<Box<dyn Stream<Item = Result<PlaceOrder, Error>> + Send>>, Error> {
client.place_order(order_id, contract, order).await
}
}
impl<'a> OrderBuilder<'a, AsyncMockClient> {
pub fn build_order(self) -> Result<Order, Error> {
self.build().map_err(Into::into)
}
pub async fn submit(self) -> Result<OrderId, Error> {
let client = self.client;
let contract = self.contract;
let order_id = client.next_order_id();
let order = self.build()?;
orders::submit_order(client, order_id, contract, &order).await?;
Ok(OrderId::new(order_id))
}
pub async fn analyze(mut self) -> Result<OrderState, Error> {
self.what_if = true;
let client = self.client;
let contract = self.contract;
let order_id = client.next_order_id();
let order = self.build()?;
let mut subscription = orders::place_order(client, order_id, contract, &order).await?;
while let Some(Ok(response)) = subscription.next().await {
if let PlaceOrder::OpenOrder(order_data) = response {
if order_data.order_id == order_id {
return Ok(order_data.order_state);
}
}
}
Err(Error::Simple("What-if analysis did not return order state".to_string()))
}
pub async fn submit_with_updates(self) -> Result<Pin<Box<dyn Stream<Item = OrderUpdate> + Send>>, Error> {
let client = self.client;
let contract = self.contract;
let order_id = client.next_order_id();
let order = self.build()?;
client.submit_order_with_updates(order_id, contract, &order).await
}
}
impl<'a> BracketOrderBuilder<'a, AsyncMockClient> {
pub async fn submit_all(self) -> Result<BracketOrderIds, Error> {
let client = self.parent_builder.client;
let contract = self.parent_builder.contract;
let base_id = client.next_order_id();
let orders = self.build()?;
let mut order_ids = Vec::new();
for (i, mut order) in orders.into_iter().enumerate() {
let order_id = base_id + i as i32;
order.order_id = order_id;
order_ids.push(order_id);
if i > 0 {
order.parent_id = base_id;
}
if i == 2 {
order.transmit = true;
}
orders::submit_order(client, order_id, contract, &order).await?;
}
Ok(BracketOrderIds::new(order_ids[0], order_ids[1], order_ids[2]))
}
}
#[tokio::test]
async fn test_async_order_submit() {
let client = AsyncMockClient::new();
let contract = create_stock_contract("AAPL");
client.set_next_order_id(100);
let builder = OrderBuilder::new(&client, &contract).buy(100).limit(50.00);
let order_id = builder.submit().await.unwrap();
assert_eq!(order_id.value(), 100);
let submitted = client.get_submitted_orders();
assert_eq!(submitted.len(), 1);
assert_eq!(submitted[0].0, 100);
assert_eq!(submitted[0].2.action, Action::Buy);
assert_eq!(submitted[0].2.total_quantity, 100.0);
assert_eq!(submitted[0].2.order_type, "LMT");
assert_eq!(submitted[0].2.limit_price, Some(50.00));
}
#[tokio::test]
async fn test_async_order_analyze() {
let client = AsyncMockClient::new();
let contract = create_stock_contract("AAPL");
let order_state = OrderState {
commission: Some(2.50),
initial_margin_before: Some(20000.00),
initial_margin_after: Some(25000.00),
maintenance_margin_before: Some(15000.00),
maintenance_margin_after: Some(18000.00),
..Default::default()
};
client.add_place_order_response(vec![PlaceOrder::OpenOrder(OrderData {
order_id: 100,
contract: contract.clone(),
order: Default::default(),
order_state: order_state.clone(),
})]);
let builder = OrderBuilder::new(&client, &contract).buy(100).limit(50.00);
let analysis = builder.analyze().await.unwrap();
assert_eq!(analysis.commission, Some(2.50));
assert_eq!(analysis.initial_margin_before, Some(20000.00));
assert_eq!(analysis.initial_margin_after, Some(25000.00));
assert_eq!(analysis.maintenance_margin_before, Some(15000.00));
assert_eq!(analysis.maintenance_margin_after, Some(18000.00));
let submitted = client.get_submitted_orders();
assert_eq!(submitted.len(), 1);
assert!(submitted[0].2.what_if);
}
#[tokio::test]
async fn test_async_bracket_order_submit_all() {
let client = AsyncMockClient::new();
let contract = create_stock_contract("AAPL");
client.set_next_order_id(200);
let bracket_builder = OrderBuilder::new(&client, &contract)
.buy(100)
.bracket()
.entry_limit(50.0)
.take_profit(55.0)
.stop_loss(45.0);
let bracket_ids = bracket_builder.submit_all().await.unwrap();
assert_eq!(bracket_ids.parent.value(), 200);
assert_eq!(bracket_ids.take_profit.value(), 201);
assert_eq!(bracket_ids.stop_loss.value(), 202);
let submitted = client.get_submitted_orders();
assert_eq!(submitted.len(), 3);
assert_eq!(submitted[0].0, 200);
assert_eq!(submitted[0].2.action, Action::Buy);
assert_eq!(submitted[0].2.order_type, "LMT");
assert_eq!(submitted[0].2.limit_price, Some(50.0));
assert!(!submitted[0].2.transmit);
assert_eq!(submitted[1].0, 201);
assert_eq!(submitted[1].2.action, Action::Sell);
assert_eq!(submitted[1].2.order_type, "LMT");
assert_eq!(submitted[1].2.limit_price, Some(55.0));
assert_eq!(submitted[1].2.parent_id, 200);
assert!(!submitted[1].2.transmit);
assert_eq!(submitted[2].0, 202);
assert_eq!(submitted[2].2.action, Action::Sell);
assert_eq!(submitted[2].2.order_type, "STP");
assert_eq!(submitted[2].2.aux_price, Some(45.0));
assert_eq!(submitted[2].2.parent_id, 200);
assert!(submitted[2].2.transmit);
}
#[tokio::test]
async fn test_async_submit_oca_orders() {
let client = AsyncMockClient::new();
let contract1 = create_stock_contract("AAPL");
let contract2 = create_stock_contract("MSFT");
client.set_next_order_id(300);
let order1 = OrderBuilder::new(&client, &contract1)
.buy(100)
.limit(50.0)
.oca_group("TestOCA", 1)
.build_order()
.unwrap();
let order2 = OrderBuilder::new(&client, &contract2)
.buy(100)
.limit(45.0)
.oca_group("TestOCA", 1)
.build_order()
.unwrap();
let order_ids = client.submit_oca_orders(vec![(contract1, order1), (contract2, order2)]).await.unwrap();
assert_eq!(order_ids.len(), 2);
assert_eq!(order_ids[0].value(), 300);
assert_eq!(order_ids[1].value(), 301);
let submitted = client.get_submitted_orders();
assert_eq!(submitted.len(), 2);
assert_eq!(submitted[0].2.oca_group, "TestOCA");
assert_eq!(submitted[1].2.oca_group, "TestOCA");
}
#[tokio::test]
async fn test_async_order_submit_with_error() {
let client = AsyncMockClient::new();
let contract = create_stock_contract("AAPL");
client.add_submit_order_response(Err(Error::Simple("Order rejected".to_string())));
let builder = OrderBuilder::new(&client, &contract).buy(100).limit(50.00);
let result = builder.submit().await;
assert!(result.is_err());
assert!(result.unwrap_err().to_string().contains("Order rejected"));
}
#[tokio::test]
async fn test_async_analyze_no_response() {
let client = AsyncMockClient::new();
let contract = create_stock_contract("AAPL");
client.add_place_order_response(vec![]);
let builder = OrderBuilder::new(&client, &contract).buy(100).limit(50.00);
let result = builder.analyze().await;
assert!(result.is_err());
assert!(result.unwrap_err().to_string().contains("What-if analysis did not return order state"));
}
#[tokio::test]
async fn test_async_order_update_stream() {
let client = AsyncMockClient::new();
let contract = create_stock_contract("AAPL");
client.add_order_update_stream(vec![
OrderUpdate::OrderStatus(OrderStatus {
order_id: 100,
status: "PendingSubmit".to_string(),
filled: 0.0,
remaining: 100.0,
average_fill_price: 0.0,
perm_id: 12345,
parent_id: 0,
last_fill_price: 0.0,
client_id: 0,
why_held: String::new(),
market_cap_price: 0.0,
}),
OrderUpdate::OrderStatus(OrderStatus {
order_id: 100,
status: "Submitted".to_string(),
filled: 0.0,
remaining: 100.0,
average_fill_price: 0.0,
perm_id: 12345,
parent_id: 0,
last_fill_price: 0.0,
client_id: 0,
why_held: String::new(),
market_cap_price: 0.0,
}),
OrderUpdate::OrderStatus(OrderStatus {
order_id: 100,
status: "Filled".to_string(),
filled: 100.0,
remaining: 0.0,
average_fill_price: 50.00,
perm_id: 12345,
parent_id: 0,
last_fill_price: 50.00,
client_id: 0,
why_held: String::new(),
market_cap_price: 0.0,
}),
]);
client.set_next_order_id(100);
let builder = OrderBuilder::new(&client, &contract).buy(100).limit(50.00);
let mut update_stream = builder.submit_with_updates().await.unwrap();
let mut updates = Vec::new();
while let Some(update) = update_stream.next().await {
updates.push(update);
}
assert_eq!(updates.len(), 3);
if let OrderUpdate::OrderStatus(status) = &updates[0] {
assert_eq!(status.status, "PendingSubmit");
}
if let OrderUpdate::OrderStatus(status) = &updates[1] {
assert_eq!(status.status, "Submitted");
}
if let OrderUpdate::OrderStatus(status) = &updates[2] {
assert_eq!(status.status, "Filled");
assert_eq!(status.filled, 100.0);
assert_eq!(status.average_fill_price, 50.00);
}
}
#[tokio::test]
async fn test_async_complex_order_types() {
let client = AsyncMockClient::new();
let contract = create_stock_contract("AAPL");
let builder = OrderBuilder::new(&client, &contract).sell(100).trailing_stop(5.0, 95.0);
let _order_id = builder.submit().await.unwrap();
let submitted = client.get_submitted_orders();
assert_eq!(submitted[0].2.order_type, "TRAIL");
assert_eq!(submitted[0].2.trailing_percent, Some(5.0));
assert_eq!(submitted[0].2.trail_stop_price, Some(95.0));
let builder = OrderBuilder::new(&client, &contract)
.buy(100)
.limit(50.00)
.algo("VWAP")
.algo_param("startTime", "09:30:00")
.algo_param("endTime", "16:00:00");
builder.submit().await.unwrap();
let submitted = client.get_submitted_orders();
let last = submitted.last().unwrap();
assert_eq!(last.2.algo_strategy, "VWAP");
assert_eq!(last.2.algo_params.len(), 2);
}
#[tokio::test]
async fn test_async_order_validation() {
let client = AsyncMockClient::new();
let contract = create_stock_contract("AAPL");
let builder = OrderBuilder::new(&client, &contract).buy(-100).market();
let result = builder.submit().await;
assert!(result.is_err());
assert!(result.unwrap_err().to_string().contains("Invalid quantity"));
let builder = OrderBuilder::new(&client, &contract).buy(100).limit(f64::NAN);
let result = builder.submit().await;
assert!(result.is_err());
assert!(result.unwrap_err().to_string().contains("Invalid price"));
}