Use case · Chain analytics
Derive DEX, GEX, and flow walls from a live chain
A live ChainState goes in; derive_chain_exposures returns a deterministic report carrying per-contract and per-strike / per-expiry DEX / GEX / VEX / vanna / charm and call/put open interest — in the units and sign you choose via an explicit ChainExposurePolicy. derive_flow_analytics adds spot ladders, flip levels, and strike / expiry walls.
When to use it
- You need dealer / customer positioning analytics (DEX, GEX, VEX, vanna, charm), OI-concentration walls, and gamma flip levels — with explicit, audited unit and sign conventions rather than hard-coded ones.
- You are streaming chain updates and need low-latency recompute —
ChainExposureState/ChainFlowStaterefresh only the dirty expiry slices off aChainRefreshReport. - You need deterministic, reproducible snapshots (stable ordering, explicit exclusion rows) suitable for audit or cross-process comparison.
Example
use ferro_risk::{
ChainExposureInputs, ChainExposureMetric, ChainExposurePolicy,
ChainExposureSignConvention, ChainContractMultiplier, DeltaExposureConvention,
GammaExposureConvention, VegaExposureConvention, VannaExposureConvention,
CharmExposureConvention, ChainMissingOpenInterestTreatment,
ChainZeroOpenInterestTreatment, ChainMissingImpliedVolatilityTreatment,
PricingModel, SurfaceMarketContext, derive_chain_exposures, strike_walls,
};
// `state` is a ChainState (built via ChainState::from_snapshot, then kept live
// with apply_update_batch). Conventions are chosen explicitly on the policy:
let policy = ChainExposurePolicy::new(
ChainContractMultiplier::new(100.0)?,
ChainExposureSignConvention::PositiveForLongOpenInterest,
ChainMissingOpenInterestTreatment::ExcludeContract,
ChainZeroOpenInterestTreatment::IncludeAsZero,
ChainMissingImpliedVolatilityTreatment::ExcludeContract,
DeltaExposureConvention::DollarDelta,
GammaExposureConvention::DollarGammaPerPoint,
VegaExposureConvention::DollarVegaPerVolPoint,
VannaExposureConvention::DollarVannaPerVolPoint,
CharmExposureConvention::DollarCharmPerDay,
);
let inputs = ChainExposureInputs::new(&state, &market, PricingModel::BlackScholesMerton, policy)?;
let report = derive_chain_exposures(&inputs)?;
// Per-strike GEX, via the metric enum's projection.
for bucket in &report.strike_buckets {
let gex = ChainExposureMetric::Gex.value(bucket.net_exposure);
println!("strike {} GEX {}", bucket.strike, gex);
}
// OI-side walls — one wall per metric.
let walls = strike_walls(&report);
let call_wall = walls.iter().find(|w| w.metric == ChainExposureMetric::OpenInterestCall);
let put_wall = walls.iter().find(|w| w.metric == ChainExposureMetric::OpenInterestPut);
# Ok::<(), ferro_risk::FerroRiskError>(())Exposure metrics
ChainExposureMetric has seven variants; metric.value(values: ChainExposureValues) -> f64projects the matching field under the policy's conventions:
| Metric | Description |
|---|---|
| Dex | Delta exposure, under the DeltaExposureConvention. |
| Gex | Gamma exposure, under the GammaExposureConvention. |
| Vex | Vega exposure, under the VegaExposureConvention. |
| Vanna | Vanna exposure, under the VannaExposureConvention. |
| Charm | Charm exposure, under the CharmExposureConvention. |
| OpenInterestCall | Aggregate call-side open interest (contracts). |
| OpenInterestPut | Aggregate put-side open interest (contracts). |
These are named Dex / Gex / Vex — not DeltaExposure / GammaExposure.
The report & flow layer
ChainExposureReport exposes public fields: contracts, excluded_contracts (explicit, with a reason), expiry_buckets, strike_buckets, and net_exposure (chain-wide totals). Walls, ladders, and flip levels live on the flow layer:
| Function | Description |
|---|---|
| strike_walls(&report) | One ChainStrikeWall per metric (the bucket with the largest |value|). |
| expiry_walls(&report) | One ChainExpiryWall per metric. |
| derive_flow_analytics(&inputs, policy) | Bundles base exposure, spot ladder, flip levels, and strike / expiry walls into a ChainFlowReport. |
| exposure_flip_levels(&ladder) | Per-metric sign changes across the spot ladder, with interpolated flip spot. |
Notes
- Incremental refresh:
ChainExposureState::refreshrecomputes only the expiries inChainRefreshReport. A full rebuild is forced only when the snapshot is full, the revision is non-sequential, or the model / policy / market context changes. - Sign & units come entirely from the policy:
ChainExposureSignConventionsets the sign;GammaExposureConvention(and siblings) set the units (ContractGamma,ShareGammaPerPoint,DollarGammaPerPoint,DollarGammaPerOnePercentMove). - Contracts excluded for missing IV but with valid OI still contribute to the call/put-OI buckets and walls, so OI walls aren't under-reported on chains with partial IV coverage.