What is the Vanna-Volga Hedge Surface and How Is It Calculated in Composite Metrics?

The Vanna-Volga hedge surface is a three-dimensional grid that quantifies the cost of neutralizing vega, vanna, and volga risks across different spot prices and implied volatilities, calculated in optionstratlib by combining moneyness-adjusted volatility deviations.

The Vanna-Volga hedge surface provides derivatives traders with a spatial view of hedging costs for vanilla option portfolios. In the optionstratlib Rust library, this composite metric is implemented as a two-dimensional grid where each point represents the cost of neutralizing key volatility Greeks. Understanding how this surface is constructed enables precise risk management across varying market conditions.

Core Concepts of the Vanna-Volga Method

The Vanna-Volga methodology addresses the limitations of simple vega hedging by accounting for how volatility sensitivity changes with both underlying price movements and volatility shifts.

The Three Benchmark Options

The classical Vanna-Volga approach uses three benchmark options to construct a hedge that eliminates vega, vanna, and volga risks simultaneously:

  • A 25-delta put (downside skew exposure)
  • An ATM option (pure volatility exposure)
  • A 25-delta call (upside skew exposure)

Hedge Cost Formula

In src/metrics/composite/vanna_volga.rs, the library employs a simplified computational model to determine hedge costs across the grid:

Cost = VannaComponent + VolgaComponent

VannaComponent = moneyness × |σ – σ_ATM| × 100
VolgaComponent = (|σ – σ_ATM|)² × 50

Where:

  • σ represents the volatility at the specific grid point
  • σ_ATM denotes the ATM volatility extracted from the option chain
  • moneyness calculates as |S – S_ATM| / S_ATM (relative distance from ATM spot)

Grid Construction Parameters

The surface generation requires defining a regular grid through four parameters:

  1. price_range – Tuple containing lower and upper spot price bounds
  2. vol_range – Tuple containing lower and upper volatility bounds (as decimals)
  3. price_steps – Number of discrete intervals along the price axis
  4. vol_steps – Number of discrete intervals along the volatility axis

Surface Data Structure

The implementation returns a Result<Surface, SurfaceError> where Surface maintains a BTreeSet<Point3D>. Each Point3D stores the coordinate triple (price, vol, cost) as x, y, and z values respectively, ensuring uniqueness and automatic ordering.

Implementation in optionstratlib

The Vanna-Volga hedge surface functionality spans multiple modules, with clear separation between the trait interface and concrete chain implementations.

Trait Definition

The VannaVolgaSurface trait in src/metrics/composite/vanna_volga.rs (lines 90-119) defines the contract for generating hedge surfaces:

pub trait VannaVolgaSurface {
    fn vanna_volga_surface(
        &self,
        price_range: (Positive, Positive),
        vol_range: (Positive, Positive),
        price_steps: usize,
        vol_steps: usize,
    ) -> Result<Surface, SurfaceError>;
}

This abstraction allows different data structures to implement the Vanna-Volga calculation logic while maintaining a consistent interface.

Concrete Implementation for OptionChain

The primary implementation resides in src/chains/chain.rs (lines 3811-3895) within the OptionChain struct. This implementation:

  1. Extracts the ATM volatility from available options
  2. Constructs the price-volatility grid
  3. Computes Vanna and Volga cost components for each point
  4. Aggregates results into a Surface structure

Step-by-Step Calculation Algorithm

The algorithm implemented in src/chains/chain.rs follows a precise sequence to generate the hedge surface:

Step 1: ATM Volatility Identification

The system locates the option with strike price closest to the underlying spot where implied volatility is non-zero. If no valid option exists, it defaults to 0.20 (20%) as the ATM volatility reference:

let atm_vol = self.options.iter()
    .filter(|opt| !opt.implied_volatility.is_zero())
    .min_by(|a, b| {
        let diff_a = (a.strike_price.to_dec() - self.underlying_price.to_dec()).abs();
        let diff_b = (b.strike_price.to_dec() - self.underlying_price.to_dec()).abs();
        diff_a.partial_cmp(&diff_b).unwrap_or(Ordering::Equal)
    })
    .map(|opt| opt.implied_volatility.to_dec())
    .unwrap_or(dec!(0.20));

Step 2: Grid Step Calculation

The price and volatility steps are computed as the interval size divided by the number of steps, with protection against zero-step division.

Step 3: Point-wise Cost Computation

For each grid coordinate (price, vol):

  • Compute moneyness: (price - S_ATM).abs() / S_ATM
  • Compute volatility difference: |vol - σ_ATM|
  • Compute Vanna cost: moneyness * vol_diff * 100
  • Compute Volga cost: vol_diff * vol_diff * 50
  • Sum to obtain Vanna-Volga cost
let moneyness = (price - self.underlying_price.to_dec()).abs()
    / self.underlying_price.to_dec();
let vol_diff = (vol - atm_vol).abs();

let vanna_cost = moneyness * vol_diff * dec!(100.0);
let volga_cost = vol_diff * vol_diff * dec!(50.0);
let vv_cost = vanna_cost + volga_cost;

Step 4: Surface Construction

Each calculated point is inserted into a BTreeSet<Point3D> to guarantee uniqueness and ordering. The final Surface is returned if points exist; otherwise, a SurfaceError::ConstructionError is emitted.

Practical Code Examples

Generating a Surface from an Option Chain

The following example demonstrates loading an option chain and computing the Vanna-Volga hedge surface:

use optionstratlib::chains::OptionChain;
use positive::pos_or_panic;
use rust_decimal_macros::dec;

fn main() -> Result<(), Box<dyn std::error::Error>> {
    // Load an option chain from a JSON file (the file must follow the library schema)
    let chain = OptionChain::load_from_json("data/options.json")?;

    // Define the grid
    let price_range = (pos_or_panic!(400.0), pos_or_panic!(500.0));
    let vol_range   = (pos_or_panic!(0.10), pos_or_panic!(0.40));

    // Build a 20×20 surface
    let surface = chain.vanna_volga_surface(price_range, vol_range, 20, 20)?;

    // Iterate over a few points (price, vol, hedge‑cost)
    for point in surface.points.iter().take(5) {
        println!(
            "S={:.2}, σ={:.2%}, cost={:.4}",
            point.x,
            point.y,
            point.z
        );
    }

    Ok(())
}

Custom Trait Implementation

For testing or alternative pricing models, you can implement the VannaVolgaSurface trait directly:

use optionstratlib::metrics::VannaVolgaSurface;
use positive::pos_or_panic;
use rust_decimal_macros::dec;

struct MySurface;

impl VannaVolgaSurface for MySurface {
    fn vanna_volga_surface(
        &self,
        price_range: (Positive, Positive),
        vol_range: (Positive, Positive),
        price_steps: usize,
        vol_steps: usize,
    ) -> Result<Surface, SurfaceError> {
        // ... (same logic as OptionChain) ...
        unimplemented!()
    }
}

fn demo() {
    let surf = MySurface;
    let price_range = (pos_or_panic!(380.0), pos_or_panic!(520.0));
    let vol_range   = (pos_or_panic!(0.05), pos_or_panic!(0.45));

    let surface = surf.vanna_volga_surface(price_range, vol_range, 10, 10).unwrap();
    println!("Generated {} points", surface.points.len());
}

This mirrors the trait contract defined in src/metrics/composite/vanna_volga.rs and can be used for unit-testing or alternative pricing models.

Key Source Files

The Vanna-Volga hedge surface implementation spans several modules within the optionstratlib repository:

File (relative to repository root) What It Contains
src/metrics/composite/vanna_volga.rs Definition of the VannaVolgaSurface trait, documentation, and tests that illustrate the expected behaviour.
src/chains/chain.rs (around line 3811) Concrete implementation of the trait for OptionChain, including the grid logic, ATM‑vol extraction, and cost calculation.
src/surfaces/mod.rs Types Surface and Point3D used to store the generated 3‑D points.
src/greeks/equations.rs Calculation of the underlying Greeks (including vanna) that feed into the composite metric.
tests/unit/chain/composite_metrics_test.rs Integration tests that validate the Vanna‑Volga surface behaviour against known expectations.

These files collectively implement the Vanna‑Volga hedge surface, expose it through a clean trait, and provide the infrastructure for visualising and analysing volatility‑smile‑aware hedging costs.

Summary

  • The Vanna-Volga hedge surface maps hedging costs across price and volatility dimensions using a simplified decomposition into vanna and volga components.
  • optionstratlib implements this in src/chains/chain.rs by extracting ATM volatility, iterating over a defined grid, and computing costs using moneyness-adjusted volatility deviations.
  • The VannaVolgaSurface trait in src/metrics/composite/vanna_volga.rs provides a reusable interface for any structure requiring volatility-smile-aware hedging analysis.
  • Each surface point represents the cumulative cost of neutralizing vega, vanna, and volga risks at specific price-volatility coordinates.

Frequently Asked Questions

How does the Vanna-Volga hedge surface differ from standard vega hedging?

Standard vega hedging only neutralizes sensitivity to parallel shifts in implied volatility. The Vanna-Volga hedge surface additionally accounts for vanna (sensitivity of delta to volatility changes) and volga (sensitivity of vega to volatility changes), providing protection against volatility smile movements and skew shifts.

What are the default values used when ATM volatility cannot be determined?

If the implementation in src/chains/chain.rs cannot locate a valid ATM option with non-zero implied volatility, it defaults to 0.20 (20%) as the ATM volatility reference. This fallback ensures surface generation continues even with incomplete chain data.

Can the Vanna-Volga surface be used for exotic options or only vanilla options?

The current implementation in optionstratlib is optimized for vanilla options through the OptionChain structure. While the VannaVolgaSurface trait can theoretically be implemented for exotic instruments, the underlying hedge-cost formula assumes standard moneyness calculations based on strike-to-spot relationships typical of vanilla contracts.

How does the grid resolution affect calculation performance?

The algorithm performs nested iteration over price_steps × vol_steps grid points. Increasing resolution improves surface smoothness but scales computation quadratically. For production use, the implementation in src/chains/chain.rs recommends balancing granularity with performance requirements, typically using 20×20 or 50×50 grids for real-time applications.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →