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 / ChainFlowState refresh only the dirty expiry slices off a ChainRefreshReport.
  • 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:

MetricDescription
DexDelta exposure, under the DeltaExposureConvention.
GexGamma exposure, under the GammaExposureConvention.
VexVega exposure, under the VegaExposureConvention.
VannaVanna exposure, under the VannaExposureConvention.
CharmCharm exposure, under the CharmExposureConvention.
OpenInterestCallAggregate call-side open interest (contracts).
OpenInterestPutAggregate put-side open interest (contracts).
Note

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:

FunctionDescription
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::refresh recomputes only the expiries in ChainRefreshReport. 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: ChainExposureSignConvention sets 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.