Effective spread and round-trip transaction cost, per quote and per spread.

FerroRisk turns a bid/ask into a typed cost read — quoted and half spread, mid, and round-trip cost — and aggregates a multi-leg spread into one round-trip cost, with degraded and unavailable markets typed rather than guessed.

Why this needs one shared risk path

!

A naive ask-minus-bid silently returns nonsense on a crossed or one-sided market.

!

Multi-leg spreads need one round-trip cost that respects per-leg quantity, not a sum of independent legs.

!

Locked and missing quotes are real states that should be represented, not collapsed to zero.

How FerroRisk handles it

01

Cost a single quote

effective_spread(bid, ask) returns a QuoteCostOutcome — Available, Degraded (locked market), or Unavailable (crossed, one-sided, or no quote).

02

Read the cost components

A QuoteCost exposes bid, ask, mid, quoted spread, half spread, round-trip cost, and relative spread as plain f64 accessors.

03

Aggregate the spread

spread_round_trip_cost(&legs) over QuoteCostLeg values returns a SpreadQuoteCostOutcome with the round-trip cost, half-spread cost, gross mid notional, and relative round-trip cost.

What makes this FerroRisk-shaped

effective_spread, effective_spread_from_prices, and spread_round_trip_cost are public exports of the microstructure module.

Locked, crossed, one-sided, and no-quote markets are typed variants, not sentinel numbers.

The quote-cost hot path is benchmarked, and aggregate overflow fails loud with a typed error.

Common concerns

What happens on a crossed or one-sided market?

The outcome is Unavailable with a typed reason — Crossed, OneSided, or NoQuote — so the caller decides how to handle it instead of consuming a wrong number.

Does the round-trip cost account for leg quantity?

Yes. Each QuoteCostLeg carries a quantity, and spread_round_trip_cost aggregates the legs into one round-trip and relative round-trip cost.