//! Table-driven test data for contracts module tests
use crate::contracts::{Contract, ContractDetails, Currency, Exchange, SecurityType, Symbol};
use crate::messages::OutgoingMessages;
use crate::server_versions;
/// Test case for contract details tests
#[allow(clippy::type_complexity)]
pub struct ContractDetailsTestCase {
pub name: &'static str,
pub contract: Contract,
pub response_messages: Vec<String>,
pub expected_request: &'static str,
pub expected_count: usize,
pub validations: Box<dyn Fn(&[ContractDetails]) + Send + Sync>,
}
/// Test case for matching symbols tests
pub struct MatchingSymbolsTestCase {
pub name: &'static str,
pub pattern: &'static str,
pub response_message: String,
pub expected_request: &'static str,
pub expected_count: usize,
}
/// Test case for market rule tests
pub struct MarketRuleTestCase {
pub name: &'static str,
pub market_rule_id: i32,
pub response_message: String,
pub expected_request: &'static str,
pub expected_price_increments: usize,
}
/// Test case for option calculation tests
pub struct OptionCalculationTestCase {
pub name: &'static str,
pub contract: Contract,
pub volatility: Option<f64>,
pub option_price: Option<f64>,
pub underlying_price: f64,
pub response_message: String,
pub expected_request_prefix: &'static str,
pub expected_price: f64,
pub expected_delta: f64,
}
/// Test case for option chain tests
pub struct OptionChainTestCase {
pub name: &'static str,
pub symbol: &'static str,
pub exchange: &'static str,
pub security_type: SecurityType,
pub contract_id: i32,
pub response_messages: Vec<String>,
pub expected_request: &'static str,
pub expected_count: usize,
}
/// Test case for verify contract tests
pub struct VerifyContractTestCase {
pub name: &'static str,
pub contract: Contract,
pub server_version: i32,
pub should_error: bool,
pub error_contains: Option<&'static str>,
}
/// Test case for StreamDecoder tests
pub struct StreamDecoderTestCase {
pub name: &'static str,
pub message: &'static str,
pub expected_result: StreamDecoderResult,
}
pub enum StreamDecoderResult {
OptionComputation { price: f64, delta: f64 },
OptionChain { exchange: String, underlying_conid: i32 },
Error(&'static str),
}
/// Test case for cancel message generation
pub struct CancelMessageTestCase {
pub name: &'static str,
pub decoder_type: &'static str,
pub request_id: Option<i32>,
pub request_type: Option<OutgoingMessages>,
pub expected_message: Result<String, &'static str>,
}
/// Test cases for contract details
pub fn contract_details_test_cases() -> Vec<ContractDetailsTestCase> {
vec![
ContractDetailsTestCase {
name: "stock contract details",
contract: Contract {
symbol: Symbol::from("TSLA"),
security_type: SecurityType::Stock,
exchange: Exchange::from("SMART"),
currency: Currency::from("USD"),
..Default::default()
},
response_messages: vec![
"10\09001\0TSLA\0STK\0\00\0\0SMART\0USD\0TSLA\0NMS\0NMS\0459200101\00.01\0\0ACTIVETIM,AD,ADJUST,ALERT,ALGO,ALLOC,AON,AVGCOST,BASKET,BENCHPX,CASHQTY,COND,CONDORDER,DARKONLY,DARKPOLL,DAY,DEACT,DEACTDIS,DEACTEOD,DIS,DUR,GAT,GTC,GTD,GTT,HID,IBKRATS,ICE,IMB,IOC,LIT,LMT,LOC,MIDPX,MIT,MKT,MOC,MTL,NGCOMB,NODARK,NONALGO,OCA,OPG,OPGREROUT,PEGBENCH,PEGMID,POSTATS,POSTONLY,PREOPGRTH,PRICECHK,REL,REL2MID,RELPCTOFS,RPI,RTH,SCALE,SCALEODD,SCALERST,SIZECHK,SNAPMID,SNAPMKT,SNAPREL,STP,STPLMT,SWEEP,TRAIL,TRAILLIT,TRAILLMT,TRAILMIT,WHATIF\0SMART,AMEX,NYSE,CBOE,PHLX,ISE,CHX,ARCA,ISLAND,DRCTEDGE,BEX,BATS,EDGEA,CSFBALGO,JEFFALGO,BYX,IEX,EDGX,FOXRIVER,PEARL,NYSENAT,LTSE,MEMX,PSX\01\00\0TESLA INC\0NASDAQ\0\0Consumer, Cyclical\0Auto Manufacturers\0Auto-Cars/Light Trucks\0US/Eastern\020221229:0400-20221229:2000;20221230:0400-20221230:2000;20221231:CLOSED;20230101:CLOSED;20230102:CLOSED;20230103:0400-20230103:2000\020221229:0930-20221229:1600;20221230:0930-20221230:1600;20221231:CLOSED;20230101:CLOSED;20230102:CLOSED;20230103:0930-20230103:1600\0\00\01\0ISIN\0US88160R1014\01\0\0\026,26,26,26,26,26,26,26,26,26,26,26,26,26,26,26,26,26,26,26,26,26,26,26\0\0COMMON\01\01\0100\0\0".to_string(),
"52\01\09001\0\0".to_string(),
],
expected_request: "9|8|9000|0|TSLA|STK||0|||SMART||USD|||0|||",
expected_count: 1,
validations: Box::new(|contracts| {
assert_eq!(contracts[0].contract.symbol, Symbol::from("TSLA"));
assert_eq!(contracts[0].contract.security_type, SecurityType::Stock);
assert_eq!(contracts[0].contract.currency, Currency::from("USD"));
assert_eq!(contracts[0].contract.contract_id, 459200101);
assert_eq!(contracts[0].long_name, "TESLA INC");
assert_eq!(contracts[0].industry, "Consumer, Cyclical");
assert_eq!(contracts[0].category, "Auto Manufacturers");
assert_eq!(contracts[0].stock_type, "COMMON");
assert_eq!(contracts[0].min_size, 1.0);
assert_eq!(contracts[0].size_increment, 1.0);
assert_eq!(contracts[0].suggested_size_increment, 100.0);
}),
},
ContractDetailsTestCase {
name: "future contract details",
contract: Contract {
symbol: Symbol::from("ES"),
security_type: SecurityType::Future,
exchange: Exchange::from("CME"),
last_trade_date_or_contract_month: "202406".to_string(),
currency: Currency::from("USD"),
..Default::default()
},
response_messages: vec![
"10\09001\0ES\0FUT\020240621\00\0\0CME\0USD\0ESM4\0CME\0CME\0551318584\00.25\050\0ACTIVETIM,AD,ADJUST,ALERT,ALGO,ALLOC,AON,AVGCOST,BASKET,BENCHPX,CASHQTY,COND,CONDORDER,DAY,DEACT,DEACTDIS,DEACTEOD,GAT,GTC,GTD,GTT,HID,ICE,IOC,LIT,LMT,LOC,MIT,MKT,MOC,MTL,NGCOMB,NONALGO,OCA,PEGBENCH,PEGMID,PEGSTK,POSTONLY,PREOPGRTH,REL,RPI,RTH,SCALE,SCALEODD,SCALERST,SNAPMID,SNAPMKT,SNAPREL,STP,STPLMT,TRAIL,TRAILLIT,TRAILLMT,TRAILMIT,WHATIF\0CME\01\00\0E-mini S&P 500\0CME\0202406\0Financial\0Indices\0Broad Market Equity Index\0US/Central\020240617:CLOSED;20240618:1700-20240618:1600;20240619:1700-20240619:1600;20240620:1700-20240620:1600;20240621:1700-20240621:0900\020240617:CLOSED;20240618:1700-20240618:1600;20240619:1700-20240619:1600;20240620:1700-20240620:1600;20240621:1700-20240621:0900\0\00\01\0\0\01\0\0\0\0\0FUT\01\01\01\00.25\0\0".to_string(),
"52\01\09001\0\0".to_string(),
],
expected_request: "9|8|9000|0|ES|FUT|202406|0|||CME||USD|||0|||",
expected_count: 1,
validations: Box::new(|contracts| {
assert_eq!(contracts[0].contract.symbol, Symbol::from("ES"));
assert_eq!(contracts[0].contract.security_type, SecurityType::Future);
assert_eq!(contracts[0].contract.exchange, Exchange::from("CME"));
assert_eq!(contracts[0].contract.contract_id, 551318584);
assert_eq!(contracts[0].contract.multiplier, "50");
assert_eq!(contracts[0].contract.last_trade_date_or_contract_month, "20240621");
assert_eq!(contracts[0].long_name, "E-mini S&P 500");
assert_eq!(contracts[0].category, "Indices");
}),
},
ContractDetailsTestCase {
name: "bond contract details",
contract: Contract {
symbol: Symbol::from("TLT"),
security_type: SecurityType::Bond,
exchange: Exchange::from("SMART"),
currency: Currency::from("USD"),
..Default::default()
},
response_messages: vec![
"10\09001\0TLT\0BOND\020420815\00\0\0SMART\0USD\0TLT\0US Treasury Bond\0TLT\012345\00.01\0\0ACTIVETIM,AD,ADJUST,ALERT,ALGO,ALLOC,AON,AVGCOST,BASKET,BENCHPX,CASHQTY,COND,CONDORDER,DAY,DEACT,DEACTDIS,DEACTEOD,GAT,GTC,GTD,GTT,HID,ICE,IMB,IOC,LIT,LMT,LOC,MIT,MKT,MOC,MTL,NGCOMB,NONALGO,OCA,PEGBENCH,PEGMID,PEGSTK,POSTONLY,PREOPGRTH,REL,RPI,RTH,SCALE,SCALEODD,SCALERST,SNAPMID,SNAPMKT,SNAPREL,STP,STPLMT,TRAIL,TRAILLIT,TRAILLMT,TRAILMIT,WHATIF\0SMART,NYSE\01\00\0US Treasury Bond\0SMART\0\0Government\0\0\0US/Eastern\020221229:0400-20221229:2000;20221230:0400-20221230:2000\020221229:0930-20221229:1600;20221230:0930-20221230:1600\0\00\01\0CUSIP\0912810TL8\01\0\0\026\020420815\0GOVT\01\01\02.25\00\020420815\020120815\020320815\0CALL\01\0Government Bond Notes\00.1\00.01\01\0".to_string(),
"52\01\09001\0\0".to_string(),
],
expected_request: "9|8|9000|0|TLT|BOND||0|||SMART||USD|||0|||",
expected_count: 1,
validations: Box::new(|contracts| {
assert_eq!(contracts[0].contract.symbol, Symbol::from("TLT"));
assert_eq!(contracts[0].contract.security_type, SecurityType::Bond);
assert_eq!(contracts[0].contract.currency, Currency::from("USD"));
assert_eq!(contracts[0].contract.contract_id, 12345);
assert_eq!(contracts[0].long_name, "US Treasury Bond");
assert_eq!(contracts[0].industry, "Government");
// Note: Bond-specific fields (cusip, coupon, maturity, etc.) are not currently
// decoded by the contract details decoder and will be empty/default values
assert_eq!(contracts[0].contract.last_trade_date_or_contract_month, "20420815");
assert_eq!(contracts[0].contract.exchange, Exchange::from("SMART"));
assert_eq!(contracts[0].market_name, "US Treasury Bond");
}),
},
ContractDetailsTestCase {
name: "stock contract details - multiple exchanges",
contract: Contract {
symbol: Symbol::from("AAPL"),
security_type: SecurityType::Stock,
exchange: Exchange::from("SMART"),
currency: Currency::from("USD"),
..Default::default()
},
response_messages: vec![
"10\09001\0AAPL\0STK\0\00\0\0SMART\0USD\0AAPL\0NASDAQ\0NMS\0265598\00.01\0\0ACTIVETIM,AD,ADJUST,ALERT,ALGO,ALLOC,AON,AVGCOST,BASKET,BENCHPX,CASHQTY,COND,CONDORDER,DARKONLY,DARKPOLL,DAY,DEACT,DEACTDIS,DEACTEOD,DIS,DUR,GAT,GTC,GTD,GTT,HID,IBKRATS,ICE,IMB,IOC,LIT,LMT,LOC,MIDPX,MIT,MKT,MOC,MTL,NGCOMB,NODARK,NONALGO,OCA,OPG,OPGREROUT,PEGBENCH,PEGMID,POSTATS,POSTONLY,PREOPGRTH,PRICECHK,REL,REL2MID,RELPCTOFS,RPI,RTH,SCALE,SCALEODD,SCALERST,SIZECHK,SNAPMID,SNAPMKT,SNAPREL,STP,STPLMT,SWEEP,TRAIL,TRAILLIT,TRAILLMT,TRAILMIT,WHATIF\0SMART,AMEX,NYSE,CBOE,PHLX,ISE,CHX,ARCA,ISLAND,DRCTEDGE,BEX,BATS,EDGEA,CSFBALGO,JEFFALGO,BYX,IEX,EDGX,FOXRIVER,PEARL,NYSENAT,LTSE,MEMX,PSX\01\00\0APPLE INC\0NASDAQ\0\0Computers\0Computers\0Computers-Electronic\0US/Eastern\020090507:0700-1830,1830-2330;20090508:CLOSED\020090507:0930-1600;20090508:CLOSED\0\00\01\0ISIN\0US0378331005\01\0\0\026,26,26,26,26,26,26,26,26,26,26,26,26,26,26,26,26,26,26,26,26,26,26,26\0\0COMMON\01\01\0100\0\0".to_string(),
"10\09001\0AAPL\0STK\0\00\0\0NYSE\0USD\0AAPL\0NYSE\0NMS\0265598\00.01\0\0ACTIVETIM,AD,ADJUST,ALERT,ALGO,ALLOC,AON,AVGCOST,BASKET,BENCHPX,CASHQTY,COND,CONDORDER,DARKONLY,DARKPOLL,DAY,DEACT,DEACTDIS,DEACTEOD,DIS,DUR,GAT,GTC,GTD,GTT,HID,IBKRATS,ICE,IMB,IOC,LIT,LMT,LOC,MIDPX,MIT,MKT,MOC,MTL,NGCOMB,NODARK,NONALGO,OCA,OPG,OPGREROUT,PEGBENCH,PEGMID,POSTATS,POSTONLY,PREOPGRTH,PRICECHK,REL,REL2MID,RELPCTOFS,RPI,RTH,SCALE,SCALEODD,SCALERST,SIZECHK,SNAPMID,SNAPMKT,SNAPREL,STP,STPLMT,SWEEP,TRAIL,TRAILLIT,TRAILLMT,TRAILMIT,WHATIF\0NYSE\01\00\0APPLE INC\0NYSE\0\0Computers\0Computers\0Computers-Electronic\0US/Eastern\020090507:0700-1830,1830-2330;20090508:CLOSED\020090507:0930-1600;20090508:CLOSED\0\00\01\0ISIN\0US0378331005\01\0\0\026,26,26,26,26,26,26,26,26,26,26,26,26,26,26,26,26,26,26,26,26,26,26,26\0\0COMMON\01\01\0100\0\0".to_string(),
"52\01\09001\0\0".to_string(),
],
expected_request: "9|8|9000|0|AAPL|STK||0|||SMART||USD|||0|||",
expected_count: 2,
validations: Box::new(|contracts| {
assert_eq!(contracts.len(), 2);
// First contract (SMART routing)
assert_eq!(contracts[0].contract.symbol, Symbol::from("AAPL"));
assert_eq!(contracts[0].contract.security_type, SecurityType::Stock);
assert_eq!(contracts[0].contract.exchange, Exchange::from("SMART"));
assert_eq!(contracts[0].contract.primary_exchange, Exchange::from("NASDAQ"));
// Second contract (NYSE)
assert_eq!(contracts[1].contract.symbol, Symbol::from("AAPL"));
assert_eq!(contracts[1].contract.security_type, SecurityType::Stock);
assert_eq!(contracts[1].contract.exchange, Exchange::from("NYSE"));
assert_eq!(contracts[1].contract.primary_exchange, Exchange::from("NYSE"));
// Both should have same contract ID
assert_eq!(contracts[0].contract.contract_id, 265598);
assert_eq!(contracts[1].contract.contract_id, 265598);
}),
},
ContractDetailsTestCase {
name: "TSLA contract details - multiple exchanges (sync_tests)",
contract: Contract::stock("TSLA").build(),
response_messages: vec![
"10\09001\0TSLA\0STK\0\00\0\0SMART\0USD\0TSLA\0NMS\0NMS\076792991\00.01\0\0ACTIVETIM,AD,ADJUST,ALERT,ALGO,ALLOC,AON,AVGCOST,BASKET,BENCHPX,CASHQTY,COND,CONDORDER,DARKONLY,DARKPOLL,DAY,DEACT,DEACTDIS,DEACTEOD,DIS,DUR,GAT,GTC,GTD,GTT,HID,IBKRATS,ICE,IMB,IOC,LIT,LMT,LOC,MIDPX,MIT,MKT,MOC,MTL,NGCOMB,NODARK,NONALGO,OCA,OPG,OPGREROUT,PEGBENCH,PEGMID,POSTATS,POSTONLY,PREOPGRTH,PRICECHK,REL,REL2MID,RELPCTOFS,RPI,RTH,SCALE,SCALEODD,SCALERST,SIZECHK,SNAPMID,SNAPMKT,SNAPREL,STP,STPLMT,SWEEP,TRAIL,TRAILLIT,TRAILLMT,TRAILMIT,WHATIF\0SMART,AMEX,NYSE,CBOE,PHLX,ISE,CHX,ARCA,ISLAND,DRCTEDGE,BEX,BATS,EDGEA,CSFBALGO,JEFFALGO,BYX,IEX,EDGX,FOXRIVER,PEARL,NYSENAT,LTSE,MEMX,PSX\01\00\0TESLA INC\0NASDAQ\0\0Consumer, Cyclical\0Auto Manufacturers\0Auto-Cars/Light Trucks\0US/Eastern\020221229:0400-20221229:2000;20221230:0400-20221230:2000;20221231:CLOSED;20230101:CLOSED;20230102:CLOSED;20230103:0400-20230103:2000\020221229:0930-20221229:1600;20221230:0930-20221230:1600;20221231:CLOSED;20230101:CLOSED;20230102:CLOSED;20230103:0930-20230103:1600\0\00\01\0ISIN\0US88160R1014\01\0\0\026,26,26,26,26,26,26,26,26,26,26,26,26,26,26,26,26,26,26,26,26,26,26,26\0\0COMMON\01\01\0100\0\0".to_string(),
"10\09001\0TSLA\0STK\0\00\0\0AMEX\0USD\0TSLA\0NMS\0NMS\076792991\00.01\0\0ACTIVETIM,AD,ADJUST,ALERT,ALLOC,AVGCOST,BASKET,BENCHPX,CASHQTY,COND,CONDORDER,DAY,DEACT,DEACTDIS,DEACTEOD,GAT,GTC,GTD,GTT,HID,IOC,LIT,LMT,MIT,MKT,MTL,NGCOMB,NONALGO,OCA,PEGBENCH,SCALE,SCALERST,SNAPMID,SNAPMKT,SNAPREL,STP,STPLMT,TRAIL,TRAILLIT,TRAILLMT,TRAILMIT,WHATIF\0SMART,AMEX,NYSE,CBOE,PHLX,ISE,CHX,ARCA,ISLAND,DRCTEDGE,BEX,BATS,EDGEA,CSFBALGO,JEFFALGO,BYX,IEX,EDGX,FOXRIVER,PEARL,NYSENAT,LTSE,MEMX,PSX\01\00\0TESLA INC\0NASDAQ\0\0Consumer, Cyclical\0Auto Manufacturers\0Auto-Cars/Light Trucks\0US/Eastern\020221229:0700-20221229:2000;20221230:0700-20221230:2000;20221231:CLOSED;20230101:CLOSED;20230102:CLOSED;20230103:0700-20230103:2000\020221229:0700-20221229:2000;20221230:0700-20221230:2000;20221231:CLOSED;20230101:CLOSED;20230102:CLOSED;20230103:0700-20230103:2000\0\00\01\0ISIN\0US88160R1014\01\0\0\026,26,26,26,26,26,26,26,26,26,26,26,26,26,26,26,26,26,26,26,26,26,26,26\0\0COMMON\01\01\0100\0\0".to_string(),
"52|1|9001||".to_string(),
],
expected_request: "9|8|9000|0|TSLA|STK||0|||SMART||USD|||0|||",
expected_count: 2,
validations: Box::new(|contracts| {
assert_eq!(contracts.len(), 2);
assert_eq!(contracts[0].contract.exchange, Exchange::from("SMART"));
assert_eq!(contracts[1].contract.exchange, Exchange::from("AMEX"));
assert_eq!(contracts[0].contract.symbol, Symbol::from("TSLA"));
assert_eq!(contracts[0].contract.security_type, SecurityType::Stock);
assert_eq!(contracts[0].contract.currency, Currency::from("USD"));
assert_eq!(contracts[0].contract.contract_id, 76792991);
assert_eq!(contracts[1].contract.contract_id, 76792991);
assert_eq!(contracts[0].order_types.len(), 70);
assert_eq!(contracts[0].order_types[0], "ACTIVETIM");
assert_eq!(contracts[1].order_types.len(), 42); // AMEX has fewer order types
}),
},
]
}
/// Test cases for matching symbols
pub fn matching_symbols_test_cases() -> Vec<MatchingSymbolsTestCase> {
vec![
MatchingSymbolsTestCase {
name: "single match",
pattern: "AAPL",
response_message: "79\09000\01\012345\0AAPL\0STK\0NASDAQ\0USD\00\0APPLE INC\0\0".to_string(),
expected_request: "81|9000|AAPL|",
expected_count: 1,
},
MatchingSymbolsTestCase {
name: "multiple matches",
pattern: "AA",
response_message: "79\09000\02\067890\0AA\0STK\0SMART\0USD\01\0OPT\0ALCOA CORP\0\012346\0AAPL\0STK\0SMART\0USD\00\0APPLE INC\0\0"
.to_string(),
expected_request: "81|9000|AA|",
expected_count: 2,
},
]
}
/// Test cases for market rule
pub fn market_rule_test_cases() -> Vec<MarketRuleTestCase> {
vec![
MarketRuleTestCase {
name: "standard market rule",
market_rule_id: 26,
response_message: "87\026\04\00\00.01\00.01\00.01\01\01\05\00.05\0".to_string(),
expected_request: "91|26|",
expected_price_increments: 4,
},
MarketRuleTestCase {
name: "complex market rule",
market_rule_id: 635,
response_message: "87\0635\03\00\00.0001\00.01\00.001\010\00.01\0".to_string(),
expected_request: "91|635|",
expected_price_increments: 3,
},
MarketRuleTestCase {
name: "market rule with 6 increments",
market_rule_id: 239,
response_message: "87\0239\06\00\00.01\00.5\00.01\01\00.01\03\00.01\010000000000\00.05\010000000000\00.1\0".to_string(),
expected_request: "91|239|",
expected_price_increments: 6,
},
]
}
/// Test cases for option calculations
pub fn option_calculation_test_cases() -> Vec<OptionCalculationTestCase> {
vec![
OptionCalculationTestCase {
name: "calculate option price",
contract: Contract {
symbol: Symbol::from("AAPL"),
security_type: SecurityType::Option,
last_trade_date_or_contract_month: "20231215".to_string(),
strike: 150.0,
right: "C".to_string(),
exchange: Exchange::from("SMART"),
currency: Currency::from("USD"),
multiplier: "100".to_string(),
..Default::default()
},
volatility: Some(0.3),
option_price: None,
underlying_price: 145.0,
response_message: "21\06\09000\013\00.3\00.42\07.85\0-0.03\00.65\0-0.002\00.98\06.87\0145.0\07.85\0".to_string(),
expected_request_prefix: "54|3|9000|0|AAPL|OPT|20231215|150|C|100|SMART||USD||0.3|145|",
expected_price: 7.85,
expected_delta: 0.42,
},
OptionCalculationTestCase {
name: "calculate implied volatility",
contract: Contract {
symbol: Symbol::from("AAPL"),
security_type: SecurityType::Option,
last_trade_date_or_contract_month: "20231215".to_string(),
strike: 150.0,
right: "C".to_string(),
exchange: Exchange::from("SMART"),
currency: Currency::from("USD"),
multiplier: "100".to_string(),
..Default::default()
},
volatility: None,
option_price: Some(5.0),
underlying_price: 145.0,
response_message: "21\06\09000\013\00.25\00.32\05.0\0-0.02\00.45\0-0.001\00.25\04.55\0145.0\05.0\0".to_string(),
expected_request_prefix: "54|3|9000|0|AAPL|OPT|20231215|150|C|100|SMART||USD||5|145|",
expected_price: 5.0,
expected_delta: 0.32,
},
]
}
/// Test cases for option chain
pub fn option_chain_test_cases() -> Vec<OptionChainTestCase> {
vec![
OptionChainTestCase {
name: "stock option chain",
symbol: "AAPL",
exchange: "SMART",
security_type: SecurityType::Stock,
contract_id: 0,
response_messages: vec![
"75\09000\0SMART\0265598\0100\00\012\020230120\020230217\020230317\020230421\020230519\020230616\020230721\020230818\020231215\020240119\020240621\020250117\031\050\055\060\065\070\075\080\085\090\095\0100\0105\0110\0115\0120\0125\0130\0135\0140\0145\0150\0155\0160\0165\0170\0175\0180\0185\0190\0195\0200\0".to_string(),
"76\09000\0".to_string(),
],
expected_request: "78|9000|AAPL|SMART|STK|0|",
expected_count: 1,
},
]
}
/// Test cases for verify contract
pub fn verify_contract_test_cases() -> Vec<VerifyContractTestCase> {
vec![
VerifyContractTestCase {
name: "valid contract",
contract: Contract {
symbol: Symbol::from("AAPL"),
security_type: SecurityType::Stock,
exchange: Exchange::from("SMART"),
currency: Currency::from("USD"),
..Default::default()
},
server_version: server_versions::SIZE_RULES,
should_error: false,
error_contains: None,
},
VerifyContractTestCase {
name: "contract with security_id - old server",
contract: Contract {
symbol: Symbol::from("AAPL"),
security_type: SecurityType::Stock,
exchange: Exchange::from("SMART"),
currency: Currency::from("USD"),
security_id: "US0378331005".to_string(),
security_id_type: "ISIN".to_string(),
..Default::default()
},
server_version: server_versions::SEC_ID_TYPE - 1,
should_error: true,
error_contains: Some("security ID type"),
},
VerifyContractTestCase {
name: "contract with trading class - old server",
contract: Contract {
symbol: Symbol::from("AAPL"),
security_type: SecurityType::Stock,
exchange: Exchange::from("SMART"),
currency: Currency::from("USD"),
trading_class: "AAPL".to_string(),
..Default::default()
},
server_version: server_versions::TRADING_CLASS - 1,
should_error: true,
error_contains: Some("trading class"),
},
]
}
/// Test cases for StreamDecoder implementations
pub fn stream_decoder_test_cases() -> Vec<StreamDecoderTestCase> {
vec![
StreamDecoderTestCase {
name: "valid option computation",
message: "21\06\09000\013\00.3\00.35\05.25\0-0.025\00.55\0-0.0015\00.3\04.75\0140.0\05.25\0",
expected_result: StreamDecoderResult::OptionComputation { price: 5.25, delta: 0.35 },
},
StreamDecoderTestCase {
name: "valid option chain",
message: "75\09000\0SMART\0265598\0100\00\02\020230120\020230217\03\050\055\060\0",
expected_result: StreamDecoderResult::OptionChain {
exchange: "SMART".to_string(),
underlying_conid: 265598,
},
},
StreamDecoderTestCase {
name: "option chain end of stream",
message: "76\09000\0",
expected_result: StreamDecoderResult::Error("EndOfStream"),
},
StreamDecoderTestCase {
name: "unexpected message type",
message: "1\09000\0unexpected\0",
expected_result: StreamDecoderResult::Error("UnexpectedResponse"),
},
]
}
/// Test cases for cancel message generation
pub fn cancel_message_test_cases() -> Vec<CancelMessageTestCase> {
vec![
CancelMessageTestCase {
name: "cancel implied volatility calculation",
decoder_type: "OptionComputation",
request_id: Some(9000),
request_type: Some(OutgoingMessages::ReqCalcImpliedVolat),
expected_message: Ok("56|1|9000|".to_string()), // CancelImpliedVolatility
},
CancelMessageTestCase {
name: "cancel option price calculation",
decoder_type: "OptionComputation",
request_id: Some(9001),
request_type: Some(OutgoingMessages::ReqCalcOptionPrice),
expected_message: Ok("57|1|9001|".to_string()), // CancelOptionPrice
},
CancelMessageTestCase {
name: "cancel option chain - not supported",
decoder_type: "OptionChain",
request_id: Some(9003),
request_type: None,
expected_message: Err("cancel not implemented"),
},
]
}
/// Test case for client method tests (tests that use the Client convenience methods)
pub struct ClientMethodTestCase {
pub name: &'static str,
pub test_type: ClientMethodTest,
pub response_messages: Vec<String>,
pub expected_request: &'static str,
pub expected_result: ClientMethodResult,
}
pub enum ClientMethodTest {
CalculateOptionPrice {
contract: Contract,
volatility: f64,
underlying_price: f64,
},
CalculateImpliedVolatility {
contract: Contract,
option_price: f64,
underlying_price: f64,
},
}
pub enum ClientMethodResult {
OptionComputation {
option_price: Option<f64>,
implied_volatility: Option<f64>,
},
}
/// Test cases for client method tests
pub fn client_method_test_cases() -> Vec<ClientMethodTestCase> {
vec![
ClientMethodTestCase {
name: "calculate option price",
test_type: ClientMethodTest::CalculateOptionPrice {
contract: Contract::option("AAPL", "20231215", 150.0, "C"),
volatility: 0.25,
underlying_price: 155.0,
},
response_messages: vec!["21|6|9000|13|0.25|0.42|85.5|-0.03|0.65|-0.002|0.98|6.87|155.0|85.5|".to_string()],
expected_request: "54|3|9000|0|AAPL|OPT|20231215|150|C||SMART||USD||0.25|155|",
expected_result: ClientMethodResult::OptionComputation {
option_price: Some(85.5),
implied_volatility: Some(0.25),
},
},
ClientMethodTestCase {
name: "calculate implied volatility",
test_type: ClientMethodTest::CalculateImpliedVolatility {
contract: Contract::option("AAPL", "20231215", 150.0, "C"),
option_price: 8.5,
underlying_price: 155.0,
},
response_messages: vec!["21|6|9000|13|0.45|0.32|8.5|-0.02|0.45|-0.001|0.25|4.55|155.0|8.5|".to_string()],
expected_request: "54|3|9000|0|AAPL|OPT|20231215|150|C||SMART||USD||8.5|155|",
expected_result: ClientMethodResult::OptionComputation {
implied_volatility: Some(0.45),
option_price: Some(8.5),
},
},
]
}
/// Test case for contract details error handling
pub struct ContractDetailsErrorTestCase {
pub name: &'static str,
pub contract: Contract,
pub response_messages: Vec<String>,
pub should_error: bool,
pub error_contains: Option<&'static str>,
pub expected_count: usize,
}
/// Test cases for contract details error handling
pub fn contract_details_error_test_cases() -> Vec<ContractDetailsErrorTestCase> {
vec![
ContractDetailsErrorTestCase {
name: "error message from server",
contract: Contract::stock("INVALID").build(),
response_messages: vec!["4|2|9000|200|Invalid contract|".to_string()],
should_error: true,
error_contains: Some("Invalid contract"),
expected_count: 0,
},
ContractDetailsErrorTestCase {
name: "empty response (no contracts found)",
contract: Contract::stock("NOEXIST").build(),
response_messages: vec!["52|1|9000||".to_string()],
should_error: false,
error_contains: None,
expected_count: 0,
},
]
}