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

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 (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 (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 (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 (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.

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.

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 (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 (lines 59-71) projects the 3-D surface onto a 2-D plane by fixing one axis.

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.
  • Use Surface::new with a BTreeSet<Point3D> for manual construction when you have raw coordinates, as defined in src/surfaces/surface.rs.
  • Leverage Point3D (from 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 (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 (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 (lines 59-71) and converts the 3-D mesh into a sorted 2-D dataset suitable for curve analysis.

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 →