Use case · Microstructure

Cost a quote and aggregate a spread's round-trip

effective_spread turns a bid/ask into a typed QuoteCostOutcome — Available, Degraded, or Unavailable — and spread_round_trip_cost aggregates a multi-leg spread into one round-trip cost, with locked, crossed, and one-sided markets represented explicitly.

When to use it

  • You need per-quote execution cost — quoted spread, half spread, mid, and round-trip cost — from a bid/ask.
  • You need one round-trip cost for a multi-leg spread that respects per-leg quantity.
  • You want locked, crossed, one-sided, and missing markets handled as typed states, not sentinel numbers.

Example

use ferro_risk::{effective_spread, spread_round_trip_cost, QuoteCostLeg, QuoteCostOutcome};

// Single quote: bid/ask are Option<f64> so a missing side is explicit.
match effective_spread(Some(1.20), Some(1.24))? {
    QuoteCostOutcome::Available(cost) => {
        println!("mid = {}  half spread = {}", cost.mid(), cost.half_spread());
        println!("round-trip cost = {}", cost.round_trip_cost());
    }
    QuoteCostOutcome::Degraded { cost, .. } => { /* locked market: bid == ask */ let _ = cost; }
    QuoteCostOutcome::Unavailable { reason } => { /* crossed / one-sided / no quote */ let _ = reason; }
}

// Multi-leg spread: one round-trip cost across the legs, quantity-aware.
let legs = vec![
    QuoteCostLeg::new(Some(1.20), Some(1.24), 1.0)?,  // long leg
    QuoteCostLeg::new(Some(0.80), Some(0.83), 1.0)?,  // short leg
];
if let Some(spread) = spread_round_trip_cost(&legs)?.cost() {
    println!("spread round-trip = {}", spread.round_trip_cost());
    println!("relative round-trip = {}", spread.relative_round_trip_cost());
}
# Ok::<(), ferro_risk::FerroRiskError>(())

Per-quote cost

effective_spread returns a QuoteCostOutcome; when it's Available (or Degraded), the inner QuoteCost exposes:

QuoteCostDescription
bid() / ask()The validated bid and ask.
mid()Mid price, (bid + ask) / 2.
quoted_spread()Ask − bid.
half_spread()Half the quoted spread — the one-way cost to mid.
round_trip_cost()Full round-trip cost of crossing the spread.
relative_spread()Quoted spread relative to mid.
QuoteCostOutcomeDescription
Available(QuoteCost)A clean two-sided market.
Degraded { cost, degradation }A usable cost with a flag — e.g. LockedMarket (bid == ask).
Unavailable { reason }No usable cost — Crossed, OneSided, or NoQuote.

Spread aggregation

spread_round_trip_cost(&[QuoteCostLeg]) returns a SpreadQuoteCostOutcome mirroring the per-quote shape; its SpreadQuoteCost exposes:

SpreadQuoteCostDescription
round_trip_cost()Aggregate round-trip cost across all legs.
half_spread_cost()Aggregate one-way (to-mid) cost.
gross_mid_notional()Gross mid notional of the spread.
relative_round_trip_cost()Round-trip cost relative to gross mid notional, in [0, 2].
leg_count()Number of legs aggregated.

Notes

  • Bid and ask are Option<f64>, so a one-sided market is OneSided, not a quote against zero. effective_spread_from_prices takes a BidAskPrices when you already have that type.
  • A crossed market (bid > ask) is Unavailable — the crate refuses to return a negative round-trip cost.
  • Each QuoteCostLeg carries a quantity, so the aggregate respects leg sizing rather than summing unit legs.
  • Aggregate overflow / underflow fails loud with a typed FerroRiskError in the quote_cost.* namespace.
Module

The microstructure primitives live in ferro_risk::microstructure and are re-exported at the crate root, so use ferro_risk::{effective_spread, spread_round_trip_cost} works directly.