# How to Construct a Volatility Surface in OptionStratLib Using the Curves and Surfaces Modules

> Learn to construct a volatility surface in OptionStratLib using curves and surfaces modules. Effortlessly build or generate surfaces from implied volatility data for robust options analysis.

- Repository: [Joaquin Bejar Garcia/optionstratlib](https://github.com/joaquinbejar/optionstratlib)
- Tags: how-to-guide
- Published: 2026-03-04

---

**You construct a volatility surface in OptionStratLib by collecting implied volatility data as `Point3D` objects (strike × time × volatility), storing them in a `BTreeSet`, and wrapping them in the `Surface` struct, which can be built manually or generated automatically via the `ImpliedVolatilitySurface` trait implemented on `OptionChain`.**

The `optionstratlib` crate provides a geometric abstraction layer that separates raw financial data from visualization logic. To construct a volatility surface, you work primarily with the **`surfaces`** module for 3-D data structures and the **`curves`** module for 2-D projections. This design allows you to either leverage the high-level `OptionChain::iv_surface` method or manually assemble surfaces from raw `Point3D` coordinates.

## Understanding the Geometry Primitives

OptionStratLib treats volatility surfaces as mathematical geometries rather than nested financial objects. This separation of concerns keeps the code maintainable and enables reuse across different asset classes.

### Point3D and Surface in the surfaces Module

The foundation of any volatility surface is the **`Point3D`** struct defined in [`src/surfaces/types.rs`](https://github.com/joaquinbejar/optionstratlib/blob/main/src/surfaces/types.rs) (lines 18-55). Each point represents a coordinate in **strike (X)**, **days to expiry (Y)**, and **implied volatility (Z)**. These points must use `Decimal` types for financial precision.

The **`Surface`** struct in [`src/surfaces/surface.rs`](https://github.com/joaquinbejar/optionstratlib/blob/main/src/surfaces/surface.rs) (lines 49-84) wraps a `BTreeSet<Point3D>` to maintain sorted order and prevent duplicates. It pre-computes X and Y ranges for efficient slicing and implements the `Graph` trait for direct plotting via Plotters or Plotly.

### Curve in the curves Module

When you need to analyze a single slice of the surface—such as the volatility smile for one expiration—you use the **`Curve`** type from [`src/curves/curve.rs`](https://github.com/joaquinbejar/optionstratlib/blob/main/src/curves/curve.rs) (lines 28-71). A `Curve` is a sorted `BTreeSet<Point2D>` that also implements `Graph`, allowing 2-D visualization and interpolation methods.

## Constructing a Volatility Surface from an OptionChain

The most common workflow uses the **`ImpliedVolatilitySurface`** trait implemented for `OptionChain` in [`src/chains/chain.rs`](https://github.com/joaquinbejar/optionstratlib/blob/main/src/chains/chain.rs) (lines 3618-3674). This approach automatically converts option prices into implied volatilities, applies the square-root-of-time scaling, and returns a ready-to-plot `Surface`.

```rust
use optionstratlib::chains::utils::OptionChainBuildParams;
use optionstratlib::prelude::*;
use positive::pos_or_panic;
use optionstratlib::error::SurfaceError;

// Build a synthetic chain
let params = OptionChainBuildParams::new(
    "SPY".to_string(),
    None,
    10,
    spos!(5.0),
    dec!(-0.10),
    dec!(0.05),
    pos_or_panic!(0.02),
    2,
    // pricing params …
);
let chain = OptionChain::build_chain(&params).unwrap();

// Define expiries for the surface
let days = vec![
    pos_or_panic!(7.0),
    pos_or_panic!(14.0),
    pos_or_panic!(30.0),
    pos_or_panic!(60.0),
];

// Generate the surface
let iv_surface = chain.iv_surface(days)?;   // Returns Surface

// Visualize
iv_surface
    .plot()
    .title("IV Surface – SPY")
    .x_label("Strike")
    .y_label("Days to Expiry")
    .z_label("Implied Vol.")
    .dimensions(1600, 1200)
    .save("./Draws/Metrics/iv_surface.png")?;

```

The `iv_surface` method handles the iteration over strikes and expiries, calculates implied volatilities using the Black-Scholes model, and constructs `Point3D` objects internally. It returns a `Result<Surface, SurfaceError>` that you can directly plot or serialize.

## Manually Building a Surface from Raw Data

For custom volatility models or imported market data, construct the surface manually using `Point3D::new` and `Surface::new`. This approach bypasses the `OptionChain` logic and gives you full control over the Z-axis values.

```rust
use std::collections::BTreeSet;
use rust_decimal_macros::dec;
use optionstratlib::surfaces::{Point3D, Surface};

let mut points = BTreeSet::new();

// Grid: strikes 100-200, expiries 7-30 days
for strike in &[dec!(100), dec!(150), dec!(200)] {
    for day in &[dec!(7), dec!(14), dec!(21), dec!(30)] {
        // Custom IV calculation
        let iv = dec!(0.2) + (*strike / dec!(100)) * dec!(0.01) - (*day * dec!(0.001));
        points.insert(Point3D::new(*strike, *day, iv));
    }
}

// Construct surface
let surface = Surface::new(points);

// Plot
surface
    .plot()
    .title("Custom Synthetic Surface")
    .x_label("Strike")
    .y_label("Days")
    .z_label("IV")
    .save("./my_surface.png")?;

```

The `Surface::new` constructor in [`src/surfaces/surface.rs`](https://github.com/joaquinbejar/optionstratlib/blob/main/src/surfaces/surface.rs) (lines 30-38) accepts any `BTreeSet<Point3D>` and performs validation to ensure the data forms a valid 3-D mesh. This method is the primitive that powers higher-level constructors like `iv_surface`.

## Slicing Surfaces into 2-D Curves for Analysis

Volatility surfaces are often analyzed as collections of term structures or skews. The **`get_curve`** method in [`src/surfaces/surface.rs`](https://github.com/joaquinbejar/optionstratlib/blob/main/src/surfaces/surface.rs) (lines 59-71) projects the 3-D surface onto a 2-D plane by fixing one axis.

```rust
use optionstratlib::surfaces::{Axis, Surface};

// Assume surface is already constructed

// Extract strike vs. IV for a specific expiry (project onto X-Z plane)
let curve_at_expiry = surface.get_curve(Axis::Y);

// Plot the 2-D curve
use optionstratlib::visualization::make_scatter;
let trace = make_scatter(&curve_at_expiry);

```

Passing `Axis::Y` collapses the time dimension, returning a `Curve` representing the volatility smile for a specific expiry. You can also pass `Axis::X` to get the term structure at a specific strike, or `Axis::Z` for strike vs. time at constant volatility.

## Summary

- **Use `OptionChain::iv_surface`** for automated construction from market data, which handles IV calculation and `Point3D` generation in [`src/chains/chain.rs`](https://github.com/joaquinbejar/optionstratlib/blob/main/src/chains/chain.rs).
- **Use `Surface::new`** with a `BTreeSet<Point3D>` for manual construction when you have raw coordinates, as defined in [`src/surfaces/surface.rs`](https://github.com/joaquinbejar/optionstratlib/blob/main/src/surfaces/surface.rs).
- **Leverage `Point3D`** (from [`src/surfaces/types.rs`](https://github.com/joaquinbejar/optionstratlib/blob/main/src/surfaces/types.rs)) as the atomic unit, ensuring you use high-precision `Decimal` types for financial calculations.
- **Slice with `get_curve`** to extract `Curve` objects for 2-D analysis, utilizing the `Axis` enum to control the projection plane.
- **Visualize via the `Graph` trait**, which both `Surface` and `Curve` implement, supporting Plotters and Plotly backends without additional boilerplate.

## Frequently Asked Questions

### What is the difference between the curves and surfaces modules in OptionStratLib?

The **`curves`** module provides 2-D geometric primitives—specifically `Point2D` and `Curve`—for representing data like volatility smiles or yield curves. The **`surfaces`** module extends this to 3-D with `Point3D` and `Surface`, enabling representation of volatility surfaces that vary by both strike and expiry. Both modules implement the `Graph` trait for consistent visualization but operate on different dimensionalities.

### How does OptionStratLib calculate implied volatility when building a surface?

When you call `chain.iv_surface(days)`, the library iterates over each option in the chain and the specified expiry days, then inverts the Black-Scholes pricing model to find the implied volatility that matches the market price. This logic resides in [`src/chains/chain.rs`](https://github.com/joaquinbejar/optionstratlib/blob/main/src/chains/chain.rs) (lines 3618-3674), which implements the `ImpliedVolatilitySurface` trait. The resulting values are scaled and stored as `Point3D` coordinates.

### Can I construct a volatility surface without using OptionChain?

Yes. You can manually instantiate `Point3D` objects with your own strike, time, and volatility data, insert them into a `BTreeSet<Point3D>`, and pass this collection to `Surface::new`. This approach is useful for loading historical volatility data, testing theoretical models, or working with asset classes not supported by the current `OptionChain` implementation. The manual construction is defined in [`src/surfaces/surface.rs`](https://github.com/joaquinbejar/optionstratlib/blob/main/src/surfaces/surface.rs) (lines 30-38).

### How do I extract a specific expiration's volatility smile from a Surface?

Use the **`get_curve`** method with `Axis::Y` to project the surface onto the strike-volatility plane for a specific time to expiry. This returns a `Curve` object containing `Point2D` values that you can plot or analyze independently. The slicing logic is implemented in [`src/surfaces/surface.rs`](https://github.com/joaquinbejar/optionstratlib/blob/main/src/surfaces/surface.rs) (lines 59-71) and converts the 3-D mesh into a sorted 2-D dataset suitable for curve analysis.