Load a vol grid
You'll load an equity's spot and a grid of vols, expiries down the side and strikes across the top, into a market, and price an option on it.
Before you start
- helixio installed: see installing.
- The spot and a grid of lognormal vols: one row per expiry, one column per strike. The program's figures are made up.
Steps
- Build the surface from the grid with
FutureOptionVolSurface(close, expiries, strikes, vols, "lognormal"). - Pin the spot and the surface under the equity's name with
with_equity_spotandwith_equity_vols. - Price an option on the name with
products.equity_option. To read a vol back off the surface, see equity spots and vols.
The comments in the program match these steps.
Rust
use chrono::NaiveDate;
use helixrs::{
AnyInstrument, CollateralSpec, EquityOption, FutureOptionVolSurface, MarketContext,
OptionKind, QuoteSheet, VolConvention,
};
type Outcome<T> = Result<T, Box<dyn std::error::Error>>;
/// The close and its SOFR swap quotes, for discounting.
const SOFR_CLOSE_DATE: (i32, u32, u32) = (2026, 10, 1);
const USD_SOFR_CLOSE: &[(&str, &str, f64)] = &[
("USD-SOFR-OIS", "1M", 0.0391),
("USD-SOFR-OIS", "3M", 0.0402),
("USD-SOFR-OIS", "1Y", 0.0439),
("USD-SOFR-OIS", "2Y", 0.0459),
("USD-SOFR-OIS", "3Y", 0.0468),
("USD-SOFR-OIS", "4Y", 0.0469),
("USD-SOFR-OIS", "5Y", 0.0471),
("USD-SOFR-OIS", "7Y", 0.0475),
("USD-SOFR-OIS", "10Y", 0.0483),
("USD-SOFR-OIS", "15Y", 0.0494),
("USD-SOFR-OIS", "20Y", 0.0499),
("USD-SOFR-OIS", "30Y", 0.0492),
];
/// Your grid's spot. The figures are made up for this guide.
const SPOT: f64 = 120.0;
fn date((year, month, day): (i32, u32, u32)) -> NaiveDate {
NaiveDate::from_ymd_opt(year, month, day).expect("a real calendar date")
}
fn main() -> Outcome<()> {
let close = date(SOFR_CLOSE_DATE);
let usd = "USD".parse()?;
// Your grid: a row of vols per expiry, a column per strike.
let expiries = vec![date((2027, 4, 1)), date((2027, 10, 1))];
let strikes = vec![100.0, 120.0, 140.0];
let vols = vec![vec![0.29, 0.26, 0.24], vec![0.28, 0.25, 0.235]];
// Build the surface from the grid and pin it, with the spot, under the name.
let surface = FutureOptionVolSurface::new(close, expiries.clone(), strikes, &vols, VolConvention::Lognormal)?;
let market = MarketContext::as_of(close)
.with_recipe_quotes(
&CollateralSpec::Currency(usd),
&QuoteSheet::from_rows(USD_SOFR_CLOSE.iter().copied())?,
)?
.with_equity_spot("ACME", SPOT)?
.with_equity_vols("ACME", surface);
// Price an at-the-money call on the name off the grid.
let call = AnyInstrument::EquityOption(Box::new(EquityOption::new(
"ACME",
usd,
OptionKind::Call,
120.0,
expiries[1],
1.0,
)?));
let npv = call.npv(&market)?;
println!("call npv {npv:.6}");
Ok(())
}Python
import datetime as dt
import helixio as hlx
# The close and its SOFR swap quotes, for discounting.
SOFR_CLOSE_DATE = dt.date(2026, 10, 1)
USD_SOFR_CLOSE = {
"USD-SOFR-OIS": {
"1M": 0.0391,
"3M": 0.0402,
"1Y": 0.0439,
"2Y": 0.0459,
"3Y": 0.0468,
"4Y": 0.0469,
"5Y": 0.0471,
"7Y": 0.0475,
"10Y": 0.0483,
"15Y": 0.0494,
"20Y": 0.0499,
"30Y": 0.0492,
},
}
# Your grid: a row of vols per expiry, a column per strike. The figures are
# made up for this guide.
SPOT = 120.0
EXPIRIES = [dt.date(2027, 4, 1), dt.date(2027, 10, 1)]
STRIKES = [100.0, 120.0, 140.0]
VOLS = [[0.29, 0.26, 0.24], [0.28, 0.25, 0.235]]
# Build the surface from the grid and pin it, with the spot, under the name.
surface = hlx.vols.FutureOptionVolSurface(SOFR_CLOSE_DATE, EXPIRIES, STRIKES, VOLS, "lognormal")
market = (
hlx.MarketContext(SOFR_CLOSE_DATE)
.with_recipe_quotes("USD", USD_SOFR_CLOSE)
.with_equity_spot("ACME", SPOT)
.with_equity_vols("ACME", surface)
)
# Price an at-the-money call on the name off the grid.
call = hlx.products.equity_option("ACME", "USD", strike=120.0, expiry=EXPIRIES[1], kind="call")
npv = call.npv(market)
print(f"call npv {npv:.6f}")call npv 14.412183
Check it worked
- The call prices to a value above zero and below the spot.
Related
- Equity spots and vols, the reference for spots and surfaces.
- Books carry their market, for pricing a book on it.
From the 0.0 line at revision bf7f2e2. Every printed number is that revision's own output.