use crate::error::SanosResult;
use crate::grid::config::StrikeGridPolicyConfig;
use crate::grid::policy::{LogMoneynessQuantiles, MarketAnchored, StrikeGridPolicy};
use crate::grid::StrikeGrid;
use crate::market::{AtmMidPolicy, OptionBook};
pub fn build_strike_grids(
book: &OptionBook,
atm: &dyn AtmMidPolicy,
cfg: &StrikeGridPolicyConfig,
) -> SanosResult<Vec<StrikeGrid>> {
build_strike_grids_with_variances(book, atm, cfg, None)
}
pub fn build_strike_grids_with_variances(
book: &OptionBook,
atm: &dyn AtmMidPolicy,
cfg: &StrikeGridPolicyConfig,
total_variances: Option<&[f64]>,
) -> SanosResult<Vec<StrikeGrid>> {
match cfg {
StrikeGridPolicyConfig::MarketAnchored(c) => {
let policy: MarketAnchored = c.to_runtime()?;
policy.build(book, atm, total_variances)
}
StrikeGridPolicyConfig::LogMoneynessQuantiles(c) => {
let policy: LogMoneynessQuantiles = c.to_runtime()?;
policy.build(book, atm, total_variances)
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::error::SanosError;
use crate::grid::config::{
AtmRefineConfig, GridSizeConfig, LogMoneynessQuantilesGridConfig, MarketAnchoredGridConfig,
WingsConfig,
};
use crate::market::{CallQuote, NearestOrLinearLogMoneyness, OptionChain};
fn sample_book() -> OptionBook {
let c1 = OptionChain::new(
0.5,
vec![
CallQuote::new(0.9, 0.22, 0.24, 1.0).unwrap(),
CallQuote::new(1.1, 0.15, 0.17, 1.0).unwrap(),
],
)
.unwrap();
let c2 = OptionChain::new(
1.0,
vec![
CallQuote::new(0.85, 0.28, 0.30, 1.0).unwrap(),
CallQuote::new(1.15, 0.11, 0.13, 1.0).unwrap(),
],
)
.unwrap();
OptionBook::new(vec![c2, c1]).unwrap()
}
#[test]
fn build_strike_grids_market_anchored_dispatches_successfully() {
let book = sample_book();
let atm = NearestOrLinearLogMoneyness::default();
let cfg = StrikeGridPolicyConfig::MarketAnchored(MarketAnchoredGridConfig::default());
let grids = build_strike_grids(&book, &atm, &cfg).unwrap();
assert_eq!(grids.len(), book.len());
}
#[test]
fn build_strike_grids_propagates_invalid_config() {
let book = sample_book();
let atm = NearestOrLinearLogMoneyness::default();
let cfg = StrikeGridPolicyConfig::MarketAnchored(MarketAnchoredGridConfig {
ensure_atm: true,
wings: WingsConfig { n_left: 2, n_right: 2, ratio: 1.0 },
atm_refine: AtmRefineConfig::default(),
grid_size: GridSizeConfig::default(),
min_strike: 1e-4,
max_strike: 1e4,
min_spacing_log: 1e-3,
});
let err = build_strike_grids(&book, &atm, &cfg).unwrap_err();
match err {
SanosError::InvalidBound { field, .. } => assert_eq!(field, "grid.wings.ratio"),
_ => panic!("unexpected error variant: {err:?}"),
}
}
#[test]
fn build_strike_grids_dispatches_log_moneyness_quantiles() {
let book = sample_book();
let atm = NearestOrLinearLogMoneyness::default();
let cfg = StrikeGridPolicyConfig::LogMoneynessQuantiles(
LogMoneynessQuantilesGridConfig::default(),
);
let total_variances = vec![0.04, 0.09];
let grids =
build_strike_grids_with_variances(&book, &atm, &cfg, Some(&total_variances)).unwrap();
assert_eq!(grids.len(), book.len());
for g in grids {
assert!(g.strikes().len() >= 3);
assert!(g.strikes().windows(2).all(|w| w[1] > w[0]));
}
}
}