use std::sync::Arc;
use crate::common::test_utils::helpers::{
assert_request, assert_tws_error_message, create_blocking_test_client, create_blocking_test_client_with_ordered_proto_responses,
decode_request_proto, proto_error_response, proto_response, request_message_count,
};
use crate::contracts::{ComboLeg, Contract, Currency, Exchange, LegAction, OptionRight, SecurityType, Symbol};
use crate::messages::IncomingMessages;
use crate::orders::{Action, ExecutionFilterSide, ExecutionSide, OrderStatusKind};
use crate::stubs::MessageBusStub;
use crate::testdata::builders::orders::{
all_open_orders_request, auto_open_orders_request, cancel_order_request, commission_report, completed_order, completed_orders_end,
completed_orders_request, execution_data, executions_request, global_cancel_request, next_valid_order_id_request, open_order,
open_orders_request, order_status, place_order_request,
};
use crate::testdata::builders::{ResponseEncoder, ResponseProtoEncoder};
use super::*;
use crate::orders::common::order_builder;
#[test]
fn place_order() {
let message_bus = Arc::new(MessageBusStub::with_ordered_responses(vec![
proto_response(
IncomingMessages::OpenOrder,
open_order().status(OrderStatusKind::PreSubmitted).encode_proto(),
),
proto_response(
IncomingMessages::OrderStatus,
order_status().status(OrderStatusKind::PreSubmitted).remaining(100.0).encode_proto(),
),
proto_response(IncomingMessages::ExecutionData, execution_data().encode_proto()),
proto_response(IncomingMessages::OpenOrder, open_order().status(OrderStatusKind::Filled).encode_proto()),
proto_response(
IncomingMessages::OrderStatus,
order_status()
.status(OrderStatusKind::Filled)
.filled(100.0)
.remaining(0.0)
.average_fill_price(Some(196.52))
.last_fill_price(Some(196.52))
.encode_proto(),
),
proto_response(IncomingMessages::OpenOrder, open_order().status(OrderStatusKind::Filled).encode_proto()),
proto_response(IncomingMessages::CommissionsReport, commission_report().encode_proto()),
]));
let client = Client::stubbed(message_bus.clone(), server_versions::SIZE_RULES);
let contract = Contract {
symbol: Symbol::from("TSLA"),
security_type: SecurityType::Stock,
exchange: Exchange::from("SMART"),
currency: Currency::from("USD"),
..Contract::default()
};
let order_id = 13;
let order = order_builder::market_order(Action::Buy, 100.0);
let result = client.place_order(order_id, &contract, &order);
assert_eq!(request_message_count(&message_bus), 1);
assert_request(
&message_bus,
0,
&place_order_request().order_id(order_id).contract(&contract).order(&order),
);
assert!(result.is_ok(), "failed to place order: {}", result.err().unwrap());
let notifications = result.unwrap();
if let Some(Ok(PlaceOrder::OpenOrder(open_order))) = notifications.next_data() {
assert_eq!(open_order.order_id, 13, "open_order.order_id");
assert_eq!(open_order.order_state.status, OrderStatusKind::PreSubmitted, "order_state.status");
} else {
assert!(false, "message[0] expected an open order notification");
}
if let Some(Ok(PlaceOrder::OrderStatus(order_status))) = notifications.next_data() {
assert_eq!(order_status.order_id, 13, "order_status.order_id");
assert_eq!(order_status.status, OrderStatusKind::PreSubmitted, "order_status.status");
assert_eq!(order_status.filled, 0.0, "order_status.filled");
assert_eq!(order_status.remaining, 100.0, "order_status.remaining");
assert_eq!(order_status.average_fill_price, Some(0.0), "order_status.average_fill_price");
assert_eq!(order_status.perm_id, 1376327563, "order_status.perm_id");
assert_eq!(order_status.parent_id, 0, "order_status.parent_id");
assert_eq!(order_status.last_fill_price, Some(0.0), "order_status.last_fill_price");
assert_eq!(order_status.client_id, 100, "order_status.client_id");
assert_eq!(order_status.why_held, "", "order_status.why_held");
assert_eq!(order_status.market_cap_price, Some(0.0), "order_status.market_cap_price");
} else {
assert!(false, "message[1] expected order status notification");
}
if let Some(Ok(PlaceOrder::ExecutionData(exec_data))) = notifications.next_data() {
assert_eq!(exec_data.execution.order_id, 13, "execution.order_id");
assert_eq!(exec_data.execution.shares, 100.0, "execution.shares");
assert_eq!(exec_data.execution.price, 196.52, "execution.price");
assert_eq!(exec_data.contract.symbol, Symbol::from("TSLA"), "contract.symbol");
} else {
assert!(false, "message[2] expected execution notification");
}
assert!(
matches!(notifications.next_data(), Some(Ok(PlaceOrder::OpenOrder(_)))),
"message[3] expected an open order notification"
);
if let Some(Ok(PlaceOrder::OrderStatus(order_status))) = notifications.next_data() {
assert_eq!(order_status.status, OrderStatusKind::Filled, "order_status.status");
assert_eq!(order_status.filled, 100.0, "order_status.filled");
assert_eq!(order_status.remaining, 0.0, "order_status.remaining");
} else {
assert!(false, "message[4] expected order status notification");
}
assert!(
matches!(notifications.next_data(), Some(Ok(PlaceOrder::OpenOrder(_)))),
"message[5] expected an open order notification"
);
if let Some(Ok(PlaceOrder::CommissionReport(report))) = notifications.next_data() {
assert_eq!(report.execution_id, "00025b46.63f8f39c.01.01", "report.execution_id");
assert_eq!(report.commission, 1.0, "report.commission");
assert_eq!(report.currency, "USD", "report.currency");
} else {
assert!(false, "message[6] expected a commission report notification");
}
}
#[test]
fn place_order_encodes_hedge_max_size() {
let message_bus = Arc::new(MessageBusStub::with_ordered_responses(vec![]));
let client = Client::stubbed(message_bus.clone(), server_versions::HEDGE_MAX_SIZE);
let contract = Contract::stock("TSLA").build();
let mut order = order_builder::market_order(Action::Buy, 100.0);
order.hedge_max_size = Some(500);
let _subscription = client.place_order(20, &contract, &order).expect("place_order should succeed");
assert_eq!(request_message_count(&message_bus), 1);
let request: crate::proto::PlaceOrderRequest = decode_request_proto(&message_bus, 0);
let proto_order = request.order.expect("request carries an order");
assert_eq!(proto_order.hedge_max_size, Some(500));
}
#[test]
fn place_order_rejects_hedge_max_size_below_gate() {
let message_bus = Arc::new(MessageBusStub::with_ordered_responses(vec![]));
let client = Client::stubbed(message_bus.clone(), server_versions::HEDGE_MAX_SIZE - 1);
let contract = Contract::stock("TSLA").build();
let mut order = order_builder::market_order(Action::Buy, 100.0);
order.hedge_max_size = Some(500);
match client.place_order(20, &contract, &order) {
Err(Error::ServerVersion(required, _, _)) => assert_eq!(required, server_versions::HEDGE_MAX_SIZE),
Err(other) => panic!("expected ServerVersion error, got {other:?}"),
Ok(_) => panic!("expected place_order to be rejected below the hedge_max_size gate"),
}
assert_eq!(request_message_count(&message_bus), 0);
}
#[test]
fn cancel_order() {
let message_bus = Arc::new(MessageBusStub::with_ordered_responses(vec![proto_response(
IncomingMessages::OrderStatus,
order_status()
.order_id(41)
.status(OrderStatusKind::Cancelled)
.remaining(100.0)
.perm_id(71270927)
.encode_proto(),
)]));
let client = Client::stubbed(message_bus.clone(), server_versions::SIZE_RULES);
let order_id = 41;
let results = client.cancel_order(order_id, "");
assert_eq!(request_message_count(&message_bus), 1);
assert_request(&message_bus, 0, &cancel_order_request().order_id(order_id));
assert!(results.is_ok(), "failed to cancel order: {}", results.err().unwrap());
let results = results.unwrap();
if let Some(Ok(CancelOrder::OrderStatus(order_status))) = results.next_data() {
assert_eq!(order_status.order_id, 41, "order_status.order_id");
assert_eq!(order_status.status, OrderStatusKind::Cancelled, "order_status.status");
assert_eq!(order_status.filled, 0.0, "order_status.filled");
assert_eq!(order_status.remaining, 100.0, "order_status.remaining");
assert_eq!(order_status.average_fill_price, Some(0.0), "order_status.average_fill_price");
assert_eq!(order_status.perm_id, 71270927, "order_status.perm_id");
assert_eq!(order_status.parent_id, 0, "order_status.parent_id");
assert_eq!(order_status.last_fill_price, Some(0.0), "order_status.last_fill_price");
assert_eq!(order_status.client_id, 100, "order_status.client_id");
assert_eq!(order_status.why_held, "", "order_status.why_held");
assert_eq!(order_status.market_cap_price, Some(0.0), "order_status.market_cap_price");
}
}
#[test]
fn cancel_order_delivers_202_as_non_terminal_notice() {
let order_id = 41;
let message_bus = Arc::new(MessageBusStub::with_ordered_responses(vec![
proto_error_response(order_id, crate::messages::ORDER_CANCELLED_CODE, "Order Canceled - reason:"),
proto_response(
IncomingMessages::OrderStatus,
order_status().order_id(order_id).status(OrderStatusKind::Cancelled).encode_proto(),
),
]));
let client = Client::stubbed(message_bus, server_versions::SIZE_RULES);
let subscription = client.cancel_order(order_id, "").expect("cancel_order failed");
match subscription.next() {
Some(Ok(crate::subscriptions::SubscriptionItem::Notice(notice))) => {
assert!(notice.is_cancellation(), "expected cancellation notice, got {notice:?}");
}
other => panic!("expected non-terminal cancellation Notice, got {other:?}"),
}
match subscription.next() {
Some(Ok(crate::subscriptions::SubscriptionItem::Data(CancelOrder::OrderStatus(status)))) => {
assert_eq!(status.status, OrderStatusKind::Cancelled);
}
other => panic!("expected OrderStatus after the 202 notice, got {other:?}"),
}
}
#[test]
fn global_cancel() {
let message_bus = Arc::new(MessageBusStub::with_responses(vec![]));
let client = Client::stubbed(message_bus.clone(), server_versions::SIZE_RULES);
let results = client.global_cancel();
assert_eq!(request_message_count(&message_bus), 1);
assert_request(&message_bus, 0, &global_cancel_request());
assert!(results.is_ok(), "failed to cancel order: {}", results.err().unwrap());
}
#[test]
fn cancel_order_cme_tagging() {
let message_bus = Arc::new(MessageBusStub::with_ordered_responses(vec![proto_response(
IncomingMessages::OrderStatus,
order_status()
.order_id(41)
.status(OrderStatusKind::Cancelled)
.remaining(100.0)
.perm_id(71270927)
.encode_proto(),
)]));
let client = Client::stubbed(message_bus.clone(), server_versions::CME_TAGGING_FIELDS);
let order_id = 41;
let results = client.cancel_order(order_id, "");
assert_request(&message_bus, 0, &cancel_order_request().order_id(order_id));
assert!(results.is_ok(), "failed to cancel order: {}", results.err().unwrap());
}
#[test]
fn global_cancel_cme_tagging() {
let message_bus = Arc::new(MessageBusStub::with_responses(vec![]));
let client = Client::stubbed(message_bus.clone(), server_versions::CME_TAGGING_FIELDS);
let results = client.global_cancel();
assert_request(&message_bus, 0, &global_cancel_request());
assert!(results.is_ok(), "failed to cancel order: {}", results.err().unwrap());
}
#[test]
fn next_valid_order_id() {
let next_valid_id_proto = crate::proto::NextValidId { order_id: Some(43) };
let message_bus = Arc::new(MessageBusStub::with_ordered_responses(vec![proto_response(
IncomingMessages::NextValidId,
prost::Message::encode_to_vec(&next_valid_id_proto),
)]));
let client = Client::stubbed(message_bus.clone(), server_versions::SIZE_RULES);
let results = client.next_valid_order_id();
assert_request(&message_bus, 0, &next_valid_order_id_request());
assert!(results.is_ok(), "failed to request next order id: {}", results.err().unwrap());
assert_eq!(43, results.unwrap(), "next order id");
}
#[test]
fn completed_orders() {
let _ = env_logger::try_init();
let message_bus = Arc::new(MessageBusStub::with_ordered_responses(vec![
proto_response(
IncomingMessages::CompletedOrder,
completed_order().trail_stop_price(Some(150.25)).encode_proto(),
),
proto_response(IncomingMessages::CompletedOrdersEnd, completed_orders_end().encode_proto()),
]));
let client = Client::stubbed(message_bus.clone(), server_versions::SIZE_RULES);
let api_only = true;
let results = client.completed_orders(api_only);
assert_request(&message_bus, 0, &completed_orders_request().api_only(api_only));
assert!(results.is_ok(), "failed to request completed orders: {}", results.err().unwrap());
let results = results.unwrap();
if let Some(Ok(Orders::OrderData(order_data))) = results.next_data() {
assert_eq!(order_data.contract.symbol, Symbol::from("AAPL"), "contract.symbol");
assert_eq!(order_data.contract.security_type, SecurityType::Stock, "contract.security_type");
assert_eq!(order_data.order.action, Action::Buy, "order.action");
assert_eq!(order_data.order.total_quantity, 100.0, "order.total_quantity");
assert_eq!(order_data.order.trail_stop_price, Some(150.25), "order.trail_stop_price");
assert_eq!(
order_data.order.shareholder, "Not an insider or substantial shareholder",
"order.shareholder"
);
assert_eq!(order_data.order_state.status, OrderStatusKind::Filled, "order_state.status");
assert_eq!(
order_data.order_state.completed_time, "20231122 10:30:00 America/Los_Angeles",
"order_state.completed_time"
);
assert_eq!(order_data.order_state.completed_status, "Filled", "order_state.completed_status");
} else {
assert!(false, "expected order data");
}
}
#[test]
fn open_orders() {
let message_bus = Arc::new(MessageBusStub::with_responses(vec![
crate::testdata::builders::orders::open_order_end().encode_pipe()
]));
let client = Client::stubbed(message_bus.clone(), server_versions::SIZE_RULES);
let results = client.open_orders();
assert_request(&message_bus, 0, &open_orders_request());
assert!(results.is_ok(), "failed to request open orders: {}", results.err().unwrap());
}
#[test]
fn all_open_orders() {
let message_bus = Arc::new(MessageBusStub::with_responses(vec![
crate::testdata::builders::orders::open_order_end().encode_pipe()
]));
let client = Client::stubbed(message_bus.clone(), server_versions::SIZE_RULES);
let results = client.all_open_orders();
assert_request(&message_bus, 0, &all_open_orders_request());
assert!(results.is_ok(), "failed to request all open orders: {}", results.err().unwrap());
}
#[test]
fn auto_open_orders() {
let message_bus = Arc::new(MessageBusStub::with_responses(vec![
crate::testdata::builders::orders::open_order_end().encode_pipe()
]));
let client = Client::stubbed(message_bus.clone(), server_versions::SIZE_RULES);
let api_only = true;
let results = client.auto_open_orders(api_only);
assert_request(&message_bus, 0, &auto_open_orders_request().auto_bind(api_only));
assert!(results.is_ok(), "failed to request auto open orders: {}", results.err().unwrap());
}
#[test]
fn executions() {
let message_bus = Arc::new(MessageBusStub::with_responses(vec![
crate::testdata::builders::orders::execution_data_end().encode_pipe(),
]));
let client = Client::stubbed(message_bus.clone(), server_versions::SIZE_RULES);
let filter = ExecutionFilter {
client_id: Some(100),
account_code: "xyz".to_owned(),
time: "yyyymmdd hh:mm:ss EST".to_owned(),
symbol: "TSLA".to_owned(),
security_type: "STK".to_owned(),
exchange: "ISLAND".to_owned(),
side: Some(ExecutionFilterSide::Buy),
..Default::default()
};
let expected_filter = ExecutionFilter {
client_id: Some(100),
account_code: "xyz".to_owned(),
time: "yyyymmdd hh:mm:ss EST".to_owned(),
symbol: "TSLA".to_owned(),
security_type: "STK".to_owned(),
exchange: "ISLAND".to_owned(),
side: Some(ExecutionFilterSide::Buy),
..Default::default()
};
let results = client.executions(filter);
assert_request(
&message_bus,
0,
&executions_request()
.request_id(crate::common::test_utils::helpers::TEST_REQ_ID_FIRST)
.filter(expected_filter),
);
assert!(results.is_ok(), "failed to request executions: {}", results.err().unwrap());
}
#[test]
fn encode_limit_order() {
let message_bus = Arc::new(MessageBusStub::with_responses(vec![]));
let client = Client::stubbed(message_bus.clone(), server_versions::SIZE_RULES);
let order_id = 12;
let contract = Contract {
security_type: SecurityType::Future,
exchange: Exchange::from("EUREX"),
currency: Currency::from("EUR"),
local_symbol: "FGBL MAR 23".to_owned(),
last_trade_date_or_contract_month: "202303".to_owned(),
..Contract::default()
};
let order = order_builder::limit_order(Action::Buy, 10.0, 500.00);
let results = client.place_order(order_id, &contract, &order);
assert_request(
&message_bus,
0,
&place_order_request().order_id(order_id).contract(&contract).order(&order),
);
assert!(results.is_ok(), "failed to place order: {}", results.err().unwrap());
}
#[test]
fn encode_combo_market_order() {
let message_bus = Arc::new(MessageBusStub::with_responses(vec![]));
let client = Client::stubbed(message_bus.clone(), server_versions::SIZE_RULES);
let order_id = 12; let contract = {
let leg_1 = ComboLeg {
contract_id: 55928698, ratio: 1,
action: LegAction::Buy,
exchange: "IPE".to_owned(),
..ComboLeg::default()
};
let leg_2 = ComboLeg {
contract_id: 55850663, ratio: 1,
action: LegAction::Sell,
exchange: "IPE".to_owned(),
..ComboLeg::default()
};
Contract {
symbol: Symbol::from("WTI"), security_type: SecurityType::Spread,
currency: Currency::from("USD"),
exchange: Exchange::from("SMART"),
combo_legs: vec![leg_1, leg_2],
..Contract::default()
}
};
let order = order_builder::combo_market_order(Action::Sell, 150.0, true);
let results = client.place_order(order_id, &contract, &order);
assert_request(
&message_bus,
0,
&place_order_request().order_id(order_id).contract(&contract).order(&order),
);
assert!(results.is_ok(), "failed to place order: {}", results.err().unwrap());
}
#[test]
fn exercise_options() {
let message_bus = Arc::new(MessageBusStub::with_ordered_responses(vec![proto_response(
IncomingMessages::OpenOrder,
open_order()
.symbol("ES")
.security_type("FOP")
.last_trade_date_or_contract_month("20250919")
.strike(5800.0)
.right("C")
.multiplier("50")
.exchange("CME")
.local_symbol("ESU5C5800")
.trading_class("ES")
.total_quantity(1.0)
.encode_proto(),
)]));
let client = Client::stubbed(message_bus.clone(), server_versions::SIZE_RULES);
let contract = Contract {
symbol: Symbol::from("ES"),
security_type: SecurityType::FuturesOption,
exchange: Exchange::from("CME"),
currency: Currency::from("USD"),
last_trade_date_or_contract_month: "20250919".to_string(),
strike: 5800.0,
right: Some(OptionRight::Call),
..Default::default()
};
let subscription = client
.exercise_options(&contract, ExerciseAction::Exercise, 1, "", false, None)
.expect("failed to exercise options");
let exercise_response = subscription.next_data();
assert!(
matches!(exercise_response, Some(Ok(ExerciseOptions::OpenOrder(_)))),
"Expected ExerciseOptions::OpenOrder, got {:?}",
exercise_response
);
assert_eq!(request_message_count(&message_bus), 1);
}
#[test]
fn submit_order() {
let message_bus = Arc::new(MessageBusStub::with_responses(vec![]));
let client = Client::stubbed(message_bus.clone(), server_versions::SIZE_RULES);
let contract = Contract {
symbol: Symbol::from("AAPL"),
security_type: SecurityType::Stock,
exchange: Exchange::from("SMART"),
currency: Currency::from("USD"),
..Contract::default()
};
let order_id = 42;
let order = order_builder::market_order(Action::Buy, 200.0);
let result = client.submit_order(order_id, &contract, &order);
assert_request(
&message_bus,
0,
&place_order_request().order_id(order_id).contract(&contract).order(&order),
);
assert!(result.is_ok(), "failed to submit order: {}", result.err().unwrap());
}
#[test]
fn order_update_stream() {
let message_bus = Arc::new(MessageBusStub::with_ordered_responses(vec![
proto_response(
IncomingMessages::OpenOrder,
open_order().status(OrderStatusKind::PreSubmitted).encode_proto(),
),
proto_response(
IncomingMessages::OrderStatus,
order_status().status(OrderStatusKind::PreSubmitted).remaining(100.0).encode_proto(),
),
proto_response(IncomingMessages::ExecutionData, execution_data().encode_proto()),
proto_response(IncomingMessages::CommissionsReport, commission_report().encode_proto()),
]));
let client = Client::stubbed(message_bus, server_versions::SIZE_RULES);
let stream = client.order_update_stream();
assert!(stream.is_ok(), "failed to create order update stream: {}", stream.err().unwrap());
let notifications = stream.unwrap();
if let Some(Ok(OrderUpdate::OpenOrder(open_order))) = notifications.next_data() {
assert_eq!(open_order.order_id, 13, "open_order.order_id");
assert_eq!(open_order.contract.symbol, Symbol::from("TSLA"), "contract.symbol");
assert_eq!(open_order.order.action, Action::Buy, "order.action");
assert_eq!(open_order.order.total_quantity, 100.0, "order.total_quantity");
assert_eq!(open_order.order_state.status, OrderStatusKind::PreSubmitted, "order_state.status");
} else {
assert!(false, "expected open order notification");
}
if let Some(Ok(OrderUpdate::OrderStatus(status))) = notifications.next_data() {
assert_eq!(status.order_id, 13, "order_status.order_id");
assert_eq!(status.status, OrderStatusKind::PreSubmitted, "order_status.status");
assert_eq!(status.filled, 0.0, "order_status.filled");
assert_eq!(status.remaining, 100.0, "order_status.remaining");
} else {
assert!(false, "expected order status notification");
}
if let Some(Ok(OrderUpdate::ExecutionData(exec_data))) = notifications.next_data() {
assert_eq!(exec_data.execution.order_id, 13, "execution.order_id");
assert_eq!(exec_data.execution.shares, 100.0, "execution.shares");
assert_eq!(exec_data.execution.price, 196.52, "execution.price");
assert_eq!(exec_data.execution.side, ExecutionSide::Bought, "execution.side");
} else {
assert!(false, "expected execution data notification");
}
if let Some(Ok(OrderUpdate::CommissionReport(report))) = notifications.next_data() {
assert_eq!(report.execution_id, "00025b46.63f8f39c.01.01", "report.execution_id");
assert_eq!(report.commission, 1.0, "report.commission");
assert_eq!(report.currency, "USD", "report.currency");
} else {
assert!(false, "expected commission report notification");
}
}
#[test]
fn order_update_stream_survives_unknown_status() {
let message_bus = Arc::new(MessageBusStub::with_ordered_responses(vec![
proto_response(
IncomingMessages::OrderStatus,
order_status()
.order_id(1)
.status(OrderStatusKind::Unknown("PendingReplace".into()))
.encode_proto(),
),
proto_response(
IncomingMessages::OrderStatus,
order_status().order_id(1).status(OrderStatusKind::Filled).encode_proto(),
),
]));
let client = Client::stubbed(message_bus, server_versions::SIZE_RULES);
let stream = client.order_update_stream().expect("failed to create stream");
match stream.next_data() {
Some(Ok(OrderUpdate::OrderStatus(s))) => {
assert_eq!(s.status, OrderStatusKind::Unknown("PendingReplace".into()));
}
other => panic!("expected OrderStatus with Unknown status, got {other:?}"),
}
match stream.next_data() {
Some(Ok(OrderUpdate::OrderStatus(s))) => {
assert_eq!(s.status, OrderStatusKind::Filled);
}
other => panic!("stream did not survive the unknown status, got {other:?}"),
}
}
#[test]
fn order_update_stream_already_subscribed() {
let message_bus = Arc::new(MessageBusStub::with_responses(vec![]));
let client = Client::stubbed(message_bus, server_versions::SIZE_RULES);
let stream1 = client.order_update_stream();
assert!(stream1.is_ok(), "failed to create first order update stream");
let stream2 = client.order_update_stream();
assert!(stream2.is_err(), "second order update stream should fail");
match stream2.err().unwrap() {
Error::AlreadySubscribed => {}
other => assert!(false, "expected AlreadySubscribed error, got: {:?}", other),
}
}
#[test]
fn order_entry_point_builds_order() {
let (client, _) = create_blocking_test_client();
let contract = Contract::stock("AAPL").build();
let order = client.order(&contract).buy(100).limit(50.0).build().expect("build failed");
assert_eq!(order.action, Action::Buy);
assert_eq!(order.limit_price, Some(50.0));
}
#[test]
fn analyze_surfaces_rejected_what_if_order() {
let (client, _bus) = create_blocking_test_client_with_ordered_proto_responses(vec![proto_error_response(
9000,
201,
"Order rejected - reason:Insufficient buying power",
)]);
let contract = Contract::stock("AAPL").build();
let err = client
.order(&contract)
.buy(100)
.limit(50.0)
.analyze()
.expect_err("a rejected what-if order must surface the rejection");
assert_tws_error_message(err, 201, "Insufficient buying power");
}
#[test]
fn analyze_returns_order_state_for_the_matching_order() {
let (client, bus) = create_blocking_test_client_with_ordered_proto_responses(vec![proto_response(
IncomingMessages::OpenOrder,
open_order().order_id(90).status(OrderStatusKind::PreSubmitted).encode_proto(),
)]);
client.set_next_order_id(90);
let contract = Contract::stock("AAPL").build();
let state = client.order(&contract).buy(100).limit(50.0).analyze().expect("analyze should succeed");
assert_eq!(state.status, OrderStatusKind::PreSubmitted);
let request: crate::proto::PlaceOrderRequest = decode_request_proto(&bus, 0);
assert_eq!(request.order.expect("request carries an order").what_if, Some(true));
}
#[test]
fn analyze_reports_end_of_stream_when_no_order_arrives() {
let (client, _bus) = create_blocking_test_client_with_ordered_proto_responses(vec![]);
let contract = Contract::stock("AAPL").build();
let result = client.order(&contract).buy(100).limit(50.0).analyze();
assert!(matches!(result, Err(Error::UnexpectedEndOfStream)), "got {result:?}");
}
#[test]
fn submit_assigns_the_next_order_id_and_sends_the_order() {
let (client, bus) = create_blocking_test_client();
client.set_next_order_id(100);
let contract = Contract::stock("AAPL").build();
let order_id = client.order(&contract).buy(100).limit(50.0).submit().expect("submit should succeed");
assert_eq!(order_id.value(), 100);
assert_eq!(request_message_count(&bus), 1);
let request: crate::proto::PlaceOrderRequest = decode_request_proto(&bus, 0);
assert_eq!(request.order_id, Some(100));
let order = request.order.expect("request carries an order");
assert_eq!(order.action.as_deref(), Some("BUY"));
assert_eq!(order.order_type.as_deref(), Some("LMT"));
assert_eq!(order.lmt_price, Some(50.0));
assert!(!order.what_if.unwrap_or_default(), "submit is not a what-if order");
}
#[test]
fn submit_rejects_an_invalid_order_before_sending() {
let (client, bus) = create_blocking_test_client();
let contract = Contract::stock("AAPL").build();
let err = client
.order(&contract)
.buy(-100)
.market()
.submit()
.expect_err("a negative quantity is invalid");
assert!(err.to_string().contains("Invalid quantity"), "got {err}");
assert_eq!(request_message_count(&bus), 0, "an invalid order must not reach the wire");
}
#[test]
fn submit_all_reserves_three_ids_and_wires_the_bracket() {
let (client, bus) = create_blocking_test_client();
client.set_next_order_id(200);
let contract = Contract::stock("AAPL").build();
let ids = client
.order(&contract)
.buy(100)
.good_till_cancel()
.bracket()
.entry_limit(50.0)
.take_profit(55.0)
.stop_loss(45.0)
.submit_all()
.expect("bracket submission should succeed");
assert_eq!((ids.parent.value(), ids.take_profit.value(), ids.stop_loss.value()), (200, 201, 202));
assert_eq!(request_message_count(&bus), 3);
let orders: Vec<crate::proto::Order> = (0..3)
.map(|i| {
decode_request_proto::<crate::proto::PlaceOrderRequest>(&bus, i)
.order
.expect("request carries an order")
})
.collect();
assert_eq!(
orders.iter().map(|o| o.parent_id.unwrap_or_default()).collect::<Vec<_>>(),
vec![0, 200, 200]
);
assert_eq!(
orders.iter().map(|o| o.transmit.unwrap_or_default()).collect::<Vec<_>>(),
vec![false, false, true]
);
for order in &orders {
assert_eq!(order.tif.as_deref(), Some("GTC"));
}
assert_eq!(orders[1].action.as_deref(), Some("SELL"));
assert_eq!(orders[1].lmt_price, Some(55.0));
assert_eq!(orders[2].order_type.as_deref(), Some("STP"));
assert_eq!(orders[2].aux_price, Some(45.0));
}
#[test]
fn submit_oca_orders_numbers_each_order_and_keeps_the_group() {
let (client, bus) = create_blocking_test_client();
client.set_next_order_id(300);
let apple = Contract::stock("AAPL").build();
let microsoft = Contract::stock("MSFT").build();
let first = client
.order(&apple)
.buy(100)
.limit(50.0)
.oca_group("TestOCA", 1)
.build_order()
.expect("order should build");
let second = client
.order(µsoft)
.buy(100)
.limit(45.0)
.oca_group("TestOCA", 1)
.build_order()
.expect("order should build");
let ids = client
.submit_oca_orders(vec![(apple, first), (microsoft, second)])
.expect("OCA submission should succeed");
assert_eq!(ids.iter().map(|id| id.value()).collect::<Vec<_>>(), vec![300, 301]);
assert_eq!(request_message_count(&bus), 2);
for i in 0..2 {
let request: crate::proto::PlaceOrderRequest = decode_request_proto(&bus, i);
let order = request.order.expect("request carries an order");
assert_eq!(order.oca_group.as_deref(), Some("TestOCA"));
assert_eq!(order.oca_type, Some(1));
}
}
#[test]
fn build_order_does_not_reach_the_wire() {
let (client, bus) = create_blocking_test_client();
let contract = Contract::stock("AAPL").build();
let order = client
.order(&contract)
.sell(100)
.trailing_stop(5.0, 95.0)
.build_order()
.expect("order should build");
assert_eq!(order.order_type, "TRAIL");
assert_eq!(order.trailing_percent, Some(5.0));
assert_eq!(order.trail_stop_price, Some(95.0));
assert_eq!(request_message_count(&bus), 0);
}
#[test]
fn submit_carries_algo_parameters_to_the_wire() {
let (client, bus) = create_blocking_test_client();
let contract = Contract::stock("AAPL").build();
client
.order(&contract)
.buy(100)
.limit(50.0)
.algo("VWAP")
.algo_param("startTime", "09:30:00")
.algo_param("endTime", "16:00:00")
.submit()
.expect("submit should succeed");
let order = decode_request_proto::<crate::proto::PlaceOrderRequest>(&bus, 0)
.order
.expect("request carries an order");
assert_eq!(order.algo_strategy.as_deref(), Some("VWAP"));
assert_eq!(order.algo_params.get("startTime").map(String::as_str), Some("09:30:00"));
assert_eq!(order.algo_params.get("endTime").map(String::as_str), Some("16:00:00"));
}
#[test]
fn submit_rejects_a_non_finite_price_before_sending() {
let (client, bus) = create_blocking_test_client();
let contract = Contract::stock("AAPL").build();
let err = client.order(&contract).buy(100).limit(f64::NAN).submit().expect_err("NaN is not a price");
assert!(err.to_string().contains("Invalid price"), "got {err}");
assert_eq!(request_message_count(&bus), 0);
}