Equity spots and vols
This page lists how to give a market what an equity product reads: the share's spot price and a vol surface over expiry and strike, the calls that pin and read them, and how the surface interpolates. You need both to price an equity option or an equity total return swap.
Quick example
The example reads its close date from the desk sheets; load them once and it runs as shown. It pins a spot and a surface for a made-up name, ACME, beside the SOFR curve, reads a listed point and a point between listed points, and shows that a name the market wasn't given has no spot.
use chrono::NaiveDate;
use helixio_docs_sheets::{SOFR_CLOSE_DATE, USD_SOFR_CLOSE};
use helixrs::{
AnyInstrument, CollateralSpec, EquityOption, FutureOptionVolSurface, MarketContext,
OptionKind, QuoteSheet, VolConvention,
};
type Outcome<T> = Result<T, Box<dyn std::error::Error>>;
/// The spot and the vols are data stated here, not a published close.
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()?;
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]];
// The ACME spot and its vol surface, beside the SOFR curve.
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);
let read = market.equity_vols("ACME")?.view::<f64>();
let at_the_money = read.vol(expiries[1], 120.0);
let between = read.vol(date((2027, 7, 1)), 130.0);
println!(
"spot {:.2}",
market.equity_spot("ACME").expect("a quoted spot")
);
println!("vol Oct-27 at 120 {at_the_money:.6}");
println!("vol Jul-27 at 130 {between:.6}");
// A name the market was not given has no spot, and an option on it is refused.
println!("spot of OTHER {:?}", market.equity_spot("OTHER"));
let option = AnyInstrument::EquityOption(Box::new(EquityOption::new(
"OTHER",
usd,
OptionKind::Call,
100.0,
expiries[1],
1.0,
)?));
let refused = option.npv(&market).is_err();
println!("refused {}", if refused { "yes" } else { "no" });
Ok(())
}import datetime as dt
import helixio as hlx
from desk_sheets import SOFR_CLOSE_DATE, USD_SOFR_CLOSE
#: The spot and the vols are data stated here, not a published close.
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]]
# The ACME spot and its vol surface, beside the SOFR curve.
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)
)
read = market.equity_vols("ACME")
at_the_money = read.vol(EXPIRIES[1], 120.0)
between = read.vol(dt.date(2027, 7, 1), 130.0)
print(f"spot {market.equity_spot('ACME'):.2f}")
print(f"vol Oct-27 at 120 {at_the_money:.6f}")
print(f"vol Jul-27 at 130 {between:.6f}")
# A name the market was not given has no spot, and an option on it is refused.
print(f"spot of OTHER {market.equity_spot('OTHER')}")
option = hlx.products.equity_option("OTHER", "USD", strike=100.0, expiry=EXPIRIES[1], kind="call")
try:
option.npv(market)
refused = False
except hlx.MissingMarketInputError:
refused = True
print(f"refused {'yes' if refused else 'no'}")spot 120.00
vol Oct-27 at 120 0.250000
vol Jul-27 at 130 0.245039
spot of OTHER None
refused yes
A listed point reads back as quoted, and a point between listed points reads off the interpolation.
How it's priced
The spot is a scalar per name. The surface is a grid of vols at expiry and strike . Between strikes it interpolates linearly in vol; between expiries, linearly in total variance ; beyond every edge it is flat. Time is the ACT/365 Fixed year fraction from the surface's reference date. Where equity data comes from on a desk is covered in how a market is built.
API
| Symbol | Meaning | Rust | Python |
|---|---|---|---|
| the quoted spot | MarketContext::with_equity_spot, read by equity_spot |
MarketContext.with_equity_spot, read by equity_spot |
|
| the vol grid | FutureOptionVolSurface::new(reference, expiries, strikes, rows, convention) |
vols.FutureOptionVolSurface(reference, expiries, strikes, vols, convention) |
|
| the surface on the market | pinned under the name | MarketContext::with_equity_vols, read by equity_vols |
MarketContext.with_equity_vols, read by equity_vols |
| a vol read off the surface | surface.view::<f64>().vol(expiry, strike) |
FutureOptionVolSurface.vol(expiry, strike) |
| Argument | Type | Default | Meaning |
|---|---|---|---|
with_equity_spot(equity, price) |
str, float |
required | the spot under the equity's name |
with_equity_vols(equity, surface) |
str, FutureOptionVolSurface |
required | the vol surface under the equity's name |
FutureOptionVolSurface(reference, expiries, strikes, vols) |
date, list[date], list[float], list[list[float]] |
required | the grid: one row of vols per expiry, one column per strike |
convention |
VolConvention | str |
"lognormal" |
"lognormal" for Black vols, "normal" for Bachelier vols in price points per year |
day_count |
str | DayCountFraction |
"ACT/365.FIXED" |
the year fraction time is measured in |
FutureOptionVolSurface.flat(reference, vol) |
date, float |
required | a surface at one vol everywhere, with the same two optional arguments |
Defaults and limits
- Unknown names: a name the market wasn't given has no spot to read, and an option on it is refused rather than priced against nothing.
- Flat beyond the grid: before the first expiry, after the last, and outside the strikes, the surface returns its edge value.
- One surface type: the surface is the same listed-option surface the bond future option page uses, keyed here by an equity name rather than a contract.
See also
- Source:
crates/helixrs/src/market/store.rs, with the surface incrates/helixrs/src/marketdata/future_option_vol.rs - Related pages:
- Load a vol grid, the task step by step.
- Equity option, for what reads the spot and the surface.
- How a market is built, for where the data comes from.
From the 0.0 line at revision bf7f2e2. Every printed number is that revision's own output.