use orderbook_rs::{IVConfig, IVParams, IVQuality, OrderBook, PriceSource, SolverConfig};
use pricelevel::{Id, Side, TimeInForce};
fn create_option_book(bid_price: u128, ask_price: u128) -> OrderBook<()> {
let book = OrderBook::<()>::new("SPY-C-450-2024-03-15");
let _ = book.add_limit_order(Id::new(), bid_price, 50, Side::Buy, TimeInForce::Gtc, None);
let _ = book.add_limit_order(Id::new(), bid_price, 30, Side::Buy, TimeInForce::Gtc, None);
let _ = book.add_limit_order(Id::new(), ask_price, 40, Side::Sell, TimeInForce::Gtc, None);
let _ = book.add_limit_order(Id::new(), ask_price, 60, Side::Sell, TimeInForce::Gtc, None);
book
}
#[test]
fn test_iv_calculation_atm_call() {
let book = create_option_book(540, 550);
let params = IVParams::call(100.0, 100.0, 90.0 / 365.0, 0.05);
let config = IVConfig::default().with_price_scale(100.0);
let result = book
.implied_volatility_with_config(¶ms, PriceSource::MidPrice, &config)
.expect("IV calculation should succeed");
assert!(
result.iv > 0.15 && result.iv < 0.35,
"IV {} should be between 15% and 35%",
result.iv
);
assert!(result.is_acceptable_quality());
}
#[test]
fn test_iv_calculation_itm_put() {
let book = create_option_book(1200, 1220);
let params = IVParams::put(450.0, 460.0, 30.0 / 365.0, 0.05);
let config = IVConfig::default().with_price_scale(100.0);
let result = book
.implied_volatility_with_config(¶ms, PriceSource::MidPrice, &config)
.expect("IV calculation should succeed");
assert!(result.iv > 0.10 && result.iv < 1.0);
assert!(result.iterations < 20);
}
#[test]
fn test_iv_calculation_otm_call() {
let book = create_option_book(195, 205);
let params = IVParams::call(100.0, 110.0, 90.0 / 365.0, 0.05);
let config = IVConfig::default().with_price_scale(100.0);
let result = book
.implied_volatility_with_config(¶ms, PriceSource::MidPrice, &config)
.expect("IV calculation should succeed");
assert!(result.iv > 0.0);
assert!(result.iterations < 30);
}
#[test]
fn test_iv_quality_based_on_spread() {
let tight_book = create_option_book(500, 504); let params = IVParams::call(100.0, 100.0, 0.25, 0.05);
let config = IVConfig::default()
.with_price_scale(100.0)
.with_max_spread(2000.0);
let result = tight_book
.implied_volatility_with_config(¶ms, PriceSource::MidPrice, &config)
.expect("IV calculation should succeed");
assert_eq!(result.quality, IVQuality::High);
let medium_book = create_option_book(500, 520); let result = medium_book
.implied_volatility_with_config(¶ms, PriceSource::MidPrice, &config)
.expect("IV calculation should succeed");
assert_eq!(result.quality, IVQuality::Medium);
let wide_book = create_option_book(500, 560); let result = wide_book
.implied_volatility_with_config(¶ms, PriceSource::MidPrice, &config)
.expect("IV calculation should succeed");
assert_eq!(result.quality, IVQuality::Low);
}
#[test]
fn test_iv_with_different_price_sources() {
let book = OrderBook::<()>::new("TEST-OPT");
let _ = book.add_limit_order(Id::new(), 500, 1000, Side::Buy, TimeInForce::Gtc, None);
let _ = book.add_limit_order(Id::new(), 520, 100, Side::Sell, TimeInForce::Gtc, None);
let params = IVParams::call(100.0, 100.0, 0.25, 0.05);
let config = IVConfig::default().with_price_scale(100.0);
let mid_result = book
.implied_volatility_with_config(¶ms, PriceSource::MidPrice, &config)
.expect("IV calculation should succeed");
let weighted_result = book
.implied_volatility_with_config(¶ms, PriceSource::WeightedMid, &config)
.expect("IV calculation should succeed");
assert!(
(mid_result.price_used - weighted_result.price_used).abs() > 0.01,
"Weighted mid should differ from simple mid"
);
}
#[test]
fn test_iv_calculation_various_maturities() {
let book = create_option_book(500, 510);
let config = IVConfig::default().with_price_scale(100.0);
for days in [7, 14, 30, 60, 90, 180, 365] {
let time = days as f64 / 365.0;
let params = IVParams::call(100.0, 100.0, time, 0.05);
let result = book.implied_volatility_with_config(¶ms, PriceSource::MidPrice, &config);
assert!(
result.is_ok(),
"IV calculation should succeed for {} days maturity",
days
);
}
}
#[test]
fn test_iv_with_custom_solver_config() {
let book = create_option_book(500, 510);
let params = IVParams::call(100.0, 100.0, 0.25, 0.05);
let solver = SolverConfig::new()
.with_max_iterations(200)
.with_tolerance(1e-10)
.with_initial_guess(0.30);
let config = IVConfig::default()
.with_price_scale(100.0)
.with_solver(solver);
let result = book
.implied_volatility_with_config(¶ms, PriceSource::MidPrice, &config)
.expect("IV calculation should succeed");
assert!(result.iv > 0.0);
}
#[test]
fn test_theoretical_price_and_greeks() {
let params = IVParams::call(100.0, 100.0, 0.25, 0.05);
let vol = 0.25;
let price = OrderBook::<()>::theoretical_price(¶ms, vol);
assert!(price > 0.0);
let delta = OrderBook::<()>::option_delta(¶ms, vol);
assert!(delta > 0.4 && delta < 0.6, "ATM call delta should be ~0.5");
let gamma = OrderBook::<()>::option_gamma(¶ms, vol);
assert!(gamma > 0.0, "Gamma should be positive");
let vega = OrderBook::<()>::option_vega(¶ms, vol);
assert!(vega > 0.0, "Vega should be positive");
let theta = OrderBook::<()>::option_theta(¶ms, vol);
assert!(theta < 0.0, "Theta should be negative for long options");
}
#[test]
fn test_iv_result_methods() {
let book = create_option_book(500, 510);
let params = IVParams::call(100.0, 100.0, 0.25, 0.05);
let config = IVConfig::default().with_price_scale(100.0);
let result = book
.implied_volatility_with_config(¶ms, PriceSource::MidPrice, &config)
.expect("IV calculation should succeed");
let iv_pct = result.iv_percent();
assert!((iv_pct - result.iv * 100.0).abs() < 1e-10);
if result.spread_bps < 100.0 {
assert!(result.is_high_quality());
}
if result.spread_bps < 500.0 {
assert!(result.is_acceptable_quality());
}
}
#[test]
fn test_iv_params_moneyness() {
let itm_call = IVParams::call(110.0, 100.0, 0.25, 0.05);
assert!(itm_call.is_itm());
assert!(!itm_call.is_otm());
assert!((itm_call.intrinsic_value() - 10.0).abs() < 1e-10);
let otm_call = IVParams::call(90.0, 100.0, 0.25, 0.05);
assert!(otm_call.is_otm());
assert!(!otm_call.is_itm());
assert!(otm_call.intrinsic_value() < 1e-10);
let atm_call = IVParams::call(100.0, 100.0, 0.25, 0.05);
assert!(atm_call.is_atm());
let itm_put = IVParams::put(90.0, 100.0, 0.25, 0.05);
assert!(itm_put.is_itm());
assert!((itm_put.intrinsic_value() - 10.0).abs() < 1e-10);
let otm_put = IVParams::put(110.0, 100.0, 0.25, 0.05);
assert!(otm_put.is_otm());
}