Files
foxhunt/trading_engine/tests/order_validation_comprehensive.rs
jgrusewski 6093eac7bf 🔧 Tonic 0.14 Upgrade: Auto-generated and build system changes
Wave 64-65 cleanup: Proto regeneration and build system updates from Tonic 0.12→0.14 upgrade

Files updated:
- Cargo.lock: Dependency resolution for Tonic 0.14.2
- All build.rs: Updated for tonic-prost-build
- Proto files: Regenerated with tonic-prost 0.14
- Examples/tests: Updated for new gRPC API

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-03 07:34:26 +02:00

647 lines
20 KiB
Rust

#![allow(unused_crate_dependencies)]
//! Comprehensive trading engine order validation tests
//! Target: 95%+ coverage for order validation and business logic
use rust_decimal::Decimal;
use std::str::FromStr;
#[cfg(test)]
mod order_validation_tests {
use super::*;
// ========================================================================
// Order Price Validation Tests
// ========================================================================
#[test]
fn test_validate_order_price_positive() {
let price = Decimal::from_str("100.50").unwrap();
assert!(validate_order_price(price));
}
#[test]
fn test_validate_order_price_zero() {
let price = Decimal::from_str("0.00").unwrap();
assert!(!validate_order_price(price));
}
#[test]
fn test_validate_order_price_negative() {
let price = Decimal::from_str("-10.00").unwrap();
assert!(!validate_order_price(price));
}
#[test]
fn test_validate_order_price_tick_size() {
let price = Decimal::from_str("100.55").unwrap();
let tick_size = Decimal::from_str("0.05").unwrap();
assert!(validate_price_tick_size(price, tick_size));
let invalid_price = Decimal::from_str("100.53").unwrap();
assert!(!validate_price_tick_size(invalid_price, tick_size));
}
#[test]
fn test_validate_order_price_extreme_values() {
let very_high = Decimal::from_str("999999.99").unwrap();
assert!(validate_order_price(very_high));
let very_low = Decimal::from_str("0.0001").unwrap();
assert!(validate_order_price(very_low));
}
// ========================================================================
// Order Quantity Validation Tests
// ========================================================================
#[test]
fn test_validate_order_quantity_positive() {
let quantity = Decimal::from_str("100").unwrap();
assert!(validate_order_quantity(quantity));
}
#[test]
fn test_validate_order_quantity_zero() {
let quantity = Decimal::from_str("0").unwrap();
assert!(!validate_order_quantity(quantity));
}
#[test]
fn test_validate_order_quantity_negative() {
let quantity = Decimal::from_str("-50").unwrap();
assert!(!validate_order_quantity(quantity));
}
#[test]
fn test_validate_order_quantity_fractional() {
let quantity = Decimal::from_str("10.5").unwrap();
// Some instruments allow fractional shares
assert!(validate_order_quantity(quantity));
}
#[test]
fn test_validate_order_quantity_limits() {
let quantity = Decimal::from_str("100").unwrap();
let min_quantity = Decimal::from_str("10").unwrap();
let max_quantity = Decimal::from_str("1000").unwrap();
assert!(validate_quantity_limits(
quantity,
min_quantity,
max_quantity
));
let too_small = Decimal::from_str("5").unwrap();
assert!(!validate_quantity_limits(
too_small,
min_quantity,
max_quantity
));
let too_large = Decimal::from_str("5000").unwrap();
assert!(!validate_quantity_limits(
too_large,
min_quantity,
max_quantity
));
}
// ========================================================================
// Order Notional Value Validation Tests
// ========================================================================
#[test]
fn test_calculate_notional_value() {
let price = Decimal::from_str("100.00").unwrap();
let quantity = Decimal::from_str("50").unwrap();
let notional = calculate_notional_value(price, quantity);
assert_eq!(notional, Decimal::from_str("5000.00").unwrap());
}
#[test]
fn test_validate_notional_limits() {
let notional = Decimal::from_str("10000.00").unwrap();
let min_notional = Decimal::from_str("1000.00").unwrap();
let max_notional = Decimal::from_str("100000.00").unwrap();
assert!(validate_notional_limits(
notional,
min_notional,
max_notional
));
}
#[test]
fn test_validate_notional_below_minimum() {
let notional = Decimal::from_str("500.00").unwrap();
let min_notional = Decimal::from_str("1000.00").unwrap();
let max_notional = Decimal::from_str("100000.00").unwrap();
assert!(!validate_notional_limits(
notional,
min_notional,
max_notional
));
}
#[test]
fn test_validate_notional_above_maximum() {
let notional = Decimal::from_str("150000.00").unwrap();
let min_notional = Decimal::from_str("1000.00").unwrap();
let max_notional = Decimal::from_str("100000.00").unwrap();
assert!(!validate_notional_limits(
notional,
min_notional,
max_notional
));
}
// ========================================================================
// Order Side Validation Tests
// ========================================================================
#[test]
fn test_validate_order_side_buy() {
assert!(validate_order_side("BUY"));
assert!(validate_order_side("buy"));
assert!(validate_order_side("Buy"));
}
#[test]
fn test_validate_order_side_sell() {
assert!(validate_order_side("SELL"));
assert!(validate_order_side("sell"));
assert!(validate_order_side("Sell"));
}
#[test]
fn test_validate_order_side_invalid() {
assert!(!validate_order_side("INVALID"));
assert!(!validate_order_side(""));
assert!(!validate_order_side("BID"));
}
// ========================================================================
// Order Type Validation Tests
// ========================================================================
#[test]
fn test_validate_order_type_market() {
assert!(validate_order_type("MARKET"));
}
#[test]
fn test_validate_order_type_limit() {
assert!(validate_order_type("LIMIT"));
}
#[test]
fn test_validate_order_type_stop() {
assert!(validate_order_type("STOP"));
assert!(validate_order_type("STOP_LIMIT"));
}
#[test]
fn test_validate_order_type_invalid() {
assert!(!validate_order_type("INVALID"));
assert!(!validate_order_type(""));
}
#[test]
fn test_validate_limit_order_has_price() {
let order_type = "LIMIT";
let price = Some(Decimal::from_str("100.00").unwrap());
assert!(validate_limit_order_requirements(order_type, price));
}
#[test]
fn test_validate_limit_order_missing_price() {
let order_type = "LIMIT";
let price = None;
assert!(!validate_limit_order_requirements(order_type, price));
}
#[test]
fn test_validate_market_order_no_price_required() {
let order_type = "MARKET";
let price = None;
assert!(validate_market_order_requirements(order_type, price));
}
// ========================================================================
// Time in Force Validation Tests
// ========================================================================
#[test]
fn test_validate_time_in_force_day() {
assert!(validate_time_in_force("DAY"));
}
#[test]
fn test_validate_time_in_force_gtc() {
assert!(validate_time_in_force("GTC"));
}
#[test]
fn test_validate_time_in_force_ioc() {
assert!(validate_time_in_force("IOC"));
}
#[test]
fn test_validate_time_in_force_fok() {
assert!(validate_time_in_force("FOK"));
}
#[test]
fn test_validate_time_in_force_invalid() {
assert!(!validate_time_in_force("INVALID"));
assert!(!validate_time_in_force(""));
}
// ========================================================================
// Symbol Validation Tests
// ========================================================================
#[test]
fn test_validate_symbol_format_valid() {
assert!(validate_symbol_format("AAPL"));
assert!(validate_symbol_format("SPY"));
assert!(validate_symbol_format("MSFT"));
}
#[test]
fn test_validate_symbol_format_with_exchange() {
assert!(validate_symbol_format("AAPL.NASDAQ"));
assert!(validate_symbol_format("SPY.NYSE"));
}
#[test]
fn test_validate_symbol_format_invalid() {
assert!(!validate_symbol_format(""));
assert!(!validate_symbol_format("@#$"));
assert!(!validate_symbol_format("SYMBOL_WITH_SPACES"));
}
#[test]
fn test_validate_symbol_exists() {
let valid_symbols = vec!["AAPL", "MSFT", "SPY"];
assert!(validate_symbol_exists("AAPL", &valid_symbols));
assert!(!validate_symbol_exists("INVALID", &valid_symbols));
}
// ========================================================================
// Market Hours Validation Tests
// ========================================================================
#[test]
fn test_validate_market_hours_during_trading() {
// Assume 9:30 AM to 4:00 PM EST trading hours
let order_time = "14:30:00"; // 2:30 PM
assert!(validate_market_hours(order_time, "09:30:00", "16:00:00"));
}
#[test]
fn test_validate_market_hours_before_open() {
let order_time = "09:00:00"; // Before market open
assert!(!validate_market_hours(order_time, "09:30:00", "16:00:00"));
}
#[test]
fn test_validate_market_hours_after_close() {
let order_time = "17:00:00"; // After market close
assert!(!validate_market_hours(order_time, "09:30:00", "16:00:00"));
}
#[test]
fn test_validate_market_hours_boundary_cases() {
let open_time = "09:30:00";
let close_time = "16:00:00";
// At exact open time
assert!(validate_market_hours("09:30:00", open_time, close_time));
// At exact close time
assert!(validate_market_hours("16:00:00", open_time, close_time));
}
// ========================================================================
// Order Status Transition Validation Tests
// ========================================================================
#[test]
fn test_validate_order_status_transition_new_to_filled() {
assert!(validate_status_transition("NEW", "FILLED"));
}
#[test]
fn test_validate_order_status_transition_new_to_cancelled() {
assert!(validate_status_transition("NEW", "CANCELLED"));
}
#[test]
fn test_validate_order_status_transition_filled_to_cancelled() {
// Cannot cancel a filled order
assert!(!validate_status_transition("FILLED", "CANCELLED"));
}
#[test]
fn test_validate_order_status_transition_cancelled_to_filled() {
// Cannot fill a cancelled order
assert!(!validate_status_transition("CANCELLED", "FILLED"));
}
#[test]
fn test_validate_order_status_transition_rejected_is_terminal() {
assert!(!validate_status_transition("REJECTED", "FILLED"));
assert!(!validate_status_transition("REJECTED", "CANCELLED"));
}
// ========================================================================
// Position Limit Validation Tests
// ========================================================================
#[test]
fn test_validate_position_limit_within_bounds() {
let current_position = Decimal::from_str("500").unwrap();
let order_quantity = Decimal::from_str("300").unwrap();
let position_limit = Decimal::from_str("1000").unwrap();
assert!(validate_position_limit(
current_position,
order_quantity,
position_limit
));
}
#[test]
fn test_validate_position_limit_exceeds_limit() {
let current_position = Decimal::from_str("800").unwrap();
let order_quantity = Decimal::from_str("300").unwrap();
let position_limit = Decimal::from_str("1000").unwrap();
assert!(!validate_position_limit(
current_position,
order_quantity,
position_limit
));
}
#[test]
fn test_validate_position_limit_exact_limit() {
let current_position = Decimal::from_str("700").unwrap();
let order_quantity = Decimal::from_str("300").unwrap();
let position_limit = Decimal::from_str("1000").unwrap();
assert!(validate_position_limit(
current_position,
order_quantity,
position_limit
));
}
#[test]
fn test_validate_position_limit_short_position() {
let current_position = Decimal::from_str("-500").unwrap();
let order_quantity = Decimal::from_str("-300").unwrap(); // Adding to short
let position_limit = Decimal::from_str("1000").unwrap();
// Absolute value should be checked
let new_position = current_position + order_quantity;
assert!(new_position.abs() <= position_limit);
}
// ========================================================================
// Risk Check Validation Tests
// ========================================================================
#[test]
fn test_validate_order_risk_within_limits() {
let order_value = Decimal::from_str("10000.00").unwrap();
let available_capital = Decimal::from_str("100000.00").unwrap();
let max_order_percentage = Decimal::from_str("0.20").unwrap();
assert!(validate_order_risk(
order_value,
available_capital,
max_order_percentage
));
}
#[test]
fn test_validate_order_risk_exceeds_limit() {
let order_value = Decimal::from_str("30000.00").unwrap();
let available_capital = Decimal::from_str("100000.00").unwrap();
let max_order_percentage = Decimal::from_str("0.20").unwrap(); // 20% max
assert!(!validate_order_risk(
order_value,
available_capital,
max_order_percentage
));
}
#[test]
fn test_validate_order_risk_insufficient_capital() {
let order_value = Decimal::from_str("10000.00").unwrap();
let available_capital = Decimal::from_str("5000.00").unwrap();
let max_order_percentage = Decimal::from_str("0.50").unwrap();
assert!(!validate_order_risk(
order_value,
available_capital,
max_order_percentage
));
}
// ========================================================================
// Duplicate Order Detection Tests
// ========================================================================
#[test]
fn test_validate_no_duplicate_order() {
let order_id = "ORD123";
let existing_orders = vec!["ORD456", "ORD789"];
assert!(validate_no_duplicate(order_id, &existing_orders));
}
#[test]
fn test_validate_duplicate_order_detected() {
let order_id = "ORD123";
let existing_orders = vec!["ORD456", "ORD123", "ORD789"];
assert!(!validate_no_duplicate(order_id, &existing_orders));
}
// ========================================================================
// Order Rate Limiting Tests
// ========================================================================
#[test]
fn test_validate_order_rate_within_limit() {
let orders_count = 50;
let time_window_seconds = 60;
let max_orders_per_minute = 100;
assert!(validate_order_rate(
orders_count,
time_window_seconds,
max_orders_per_minute
));
}
#[test]
fn test_validate_order_rate_exceeds_limit() {
let orders_count = 150;
let time_window_seconds = 60;
let max_orders_per_minute = 100;
assert!(!validate_order_rate(
orders_count,
time_window_seconds,
max_orders_per_minute
));
}
// ========================================================================
// Edge Case Tests
// ========================================================================
#[test]
fn test_validate_order_with_very_small_price() {
let price = Decimal::from_str("0.0001").unwrap();
assert!(validate_order_price(price));
}
#[test]
fn test_validate_order_with_very_large_quantity() {
let quantity = Decimal::from_str("1000000").unwrap();
assert!(validate_order_quantity(quantity));
}
#[test]
fn test_validate_order_decimal_precision() {
let price = Decimal::from_str("100.123456").unwrap();
let max_precision = 2;
assert!(!validate_price_precision(price, max_precision));
let valid_price = Decimal::from_str("100.12").unwrap();
assert!(validate_price_precision(valid_price, max_precision));
}
#[test]
fn test_validate_order_symbol_case_insensitive() {
assert!(validate_symbol_format("aapl"));
assert!(validate_symbol_format("AAPL"));
assert!(validate_symbol_format("AaPl"));
}
}
// Helper validation functions
fn validate_order_price(price: Decimal) -> bool {
price > Decimal::ZERO
}
fn validate_price_tick_size(price: Decimal, tick_size: Decimal) -> bool {
(price % tick_size) == Decimal::ZERO
}
fn validate_order_quantity(quantity: Decimal) -> bool {
quantity > Decimal::ZERO
}
fn validate_quantity_limits(quantity: Decimal, min: Decimal, max: Decimal) -> bool {
quantity >= min && quantity <= max
}
fn calculate_notional_value(price: Decimal, quantity: Decimal) -> Decimal {
price * quantity
}
fn validate_notional_limits(notional: Decimal, min: Decimal, max: Decimal) -> bool {
notional >= min && notional <= max
}
fn validate_order_side(side: &str) -> bool {
let upper = side.to_uppercase();
upper == "BUY" || upper == "SELL"
}
fn validate_order_type(order_type: &str) -> bool {
let upper = order_type.to_uppercase();
matches!(upper.as_str(), "MARKET" | "LIMIT" | "STOP" | "STOP_LIMIT")
}
fn validate_limit_order_requirements(order_type: &str, price: Option<Decimal>) -> bool {
if order_type.to_uppercase() == "LIMIT" {
price.is_some()
} else {
true
}
}
fn validate_market_order_requirements(order_type: &str, _price: Option<Decimal>) -> bool {
order_type.to_uppercase() == "MARKET"
}
fn validate_time_in_force(tif: &str) -> bool {
let upper = tif.to_uppercase();
matches!(upper.as_str(), "DAY" | "GTC" | "IOC" | "FOK")
}
fn validate_symbol_format(symbol: &str) -> bool {
!symbol.is_empty() && symbol.chars().all(|c| c.is_alphanumeric() || c == '.')
}
fn validate_symbol_exists(symbol: &str, valid_symbols: &[&str]) -> bool {
valid_symbols.contains(&symbol)
}
fn validate_market_hours(order_time: &str, open: &str, close: &str) -> bool {
order_time >= open && order_time <= close
}
fn validate_status_transition(from: &str, to: &str) -> bool {
let valid_transitions = vec![
("NEW", "FILLED"),
("NEW", "PARTIALLY_FILLED"),
("NEW", "CANCELLED"),
("NEW", "REJECTED"),
("PARTIALLY_FILLED", "FILLED"),
("PARTIALLY_FILLED", "CANCELLED"),
];
valid_transitions.contains(&(from, to))
}
fn validate_position_limit(current: Decimal, order_qty: Decimal, limit: Decimal) -> bool {
(current + order_qty).abs() <= limit
}
fn validate_order_risk(order_value: Decimal, capital: Decimal, max_pct: Decimal) -> bool {
order_value <= capital && order_value <= capital * max_pct
}
fn validate_no_duplicate(order_id: &str, existing: &[&str]) -> bool {
!existing.contains(&order_id)
}
fn validate_order_rate(count: usize, window: usize, max_per_window: usize) -> bool {
if window == 0 {
return false;
}
count <= max_per_window
}
fn validate_price_precision(price: Decimal, max_decimal_places: u32) -> bool {
let scale = price.scale();
scale <= max_decimal_places
}