cdxcore.bs#

Basic Black & Scholes pricing routines.

Overview#

This module offers with the bs instance of cdxcore.bs.BS a basic Black & Scholes pricing framework for the drift-less case:

from cdxbasics.bs import bs
call = bs.price(1.,vol=0.2,sqrtT=0.1)

Aside from the respective pricing functions and greeks, bs object also offers with cdxcore.bs.BS.implied() a “mass” implied volatility solver. It can solve implied volatilities for a large number of options in one big Euler/bisection search.

The module contains the cdxcore.bs.BS, whose use case is to define the member cdxcore.bs.bs which provides the pricing functionality.

Import#

from cdxcore.bs import bs

Documentation#

Module Attributes

bs

Main instance of the cdxcore.bs.BS class.

Classes

BS()

Base class for the computation of Black & Scholes analytics in drift-less ("pure") price domain.

BSFLAGS(*values)

class cdxcore.bs.BS[source]#

Bases: object

Base class for the computation of Black & Scholes analytics in drift-less (“pure”) price domain.

The class is synthactic sugar; simply use the one instance bs via:

from cdxcore.bs import bs

Then you can do:

call = bs.price( 1., 0.2, sqrtT=0.5 )
vega = bs.vega( 1., 0.2, sqrtT=0.5 )

The most useful function is bs.implied documented under cdxcore.bs.BS.implied`().

DELTA = 2#

Flag to request Delta: N1 for a call

DK = 4#

Flag to request dK: -N2 for a call

GAMMA = 8#

Flag to request Gamma

LOGK = 64#

Flag to request logK, set to 1 where k=0 or vol*sqrtT=0

PRICE = 1#

Flag to request Price, for a call: N1 - k N2

THETA = 32#

Flag to request Theta, here defined as derivative in time-to-expiry (e.g. it is positive!).

VEGA = 16#

Flag to request Vega

__call__(k, vol, sqrtT=1.0, what=<BSFLAGS.PRICE: 1>, is_call=True, *, logK=None, eps=None)[source]#

Compute Black Scholes call option prices, and greeks in drift-less price domain efficiently.

This function aims to support almost any broadcast combination for its inputs.

The function returns values for the intrinsic call/put functions when strikes or sqrt-variances approach zero.

Example of using broadcastable shapes:

from cdxcore.bs import bs, np
sqrtT = np.array( [0.2, 0.4] ).reshape((1,2))
nms   = np.linspace( -1,+1,11 ).reshape((11,1))
k     = np.exp( nms * sqrtT )
v     = np.random.normal( size=(11,1) )**2
sqrtT = np.array( [0.2, 0.4] ).reshape((1,2))

price, vega = bs( k=k, vol=v, sqrtT=sqrtT, what=bs.PRICE|bs.VEGA )['price','vega']         
assert price.shape == (11,2)
Parameters:
knp.ndarray | float

Strikes.

volnp.ndarray | float

Volatilities or a single volatility.

sqrtTnp.ndarray | float, default 1

Square-root of time.

whatcombination of cdxcore.bs.BSFLAGS flags, default BS.PRICE

A bitmask indicating what to compute. Can be any combination of:

  • BS.PRICE : Call price

  • BS.DELTA : Delta

  • BS.DK : Derivative in strike

  • BS.VEGA : Vega

  • BS.GAMMA : Gamma

  • BS.THETA : Theta as derivative in time-to-expiry, e.g. it is positive.

  • BS.LOGK : Log-strike, with 1 whereever k==0. This is mainly useful to avoid recomputing log(k) multiple times.

Note that if only one item is requested the function returns a np.ndarray or float. Otherwise it will return a cdxcore.pretty.PrettyValueObject with the requested outputs as attributes. The following then works as expected:

from cdxcore.bs import bs
price, vega = bs( k=0.8, vol=0.2, sqrtT=1., what=bs.PRICE|bs.VEGA )['price','vega'] 

The order of items is always “price”, “delta”, “dk”, “gamma”, “vega”, “theta”, “logK” whichever was requested; hence you can also do:

price, vega = bs( k=0.8, vol=0.2, sqrtT=1., what=bs.PRICE|bs.VEGA )
logKnp.ndarray | float | None, default None

An optional pre-computed log-strike. If provided, this is used instead of computing log(k) internally. The function will mask this array where k==0, hence logK can be left NaN in those locations.

epsfloat | None, default None

An optional precision tolerance for the internal checks. If not provided, the default is bs._eps (1E-8). Note that the function will cap/floor the values of prices or greeks with the respective bounds even if the validity test passed. Hence, if eps is set to a large number, this will lead to floored/capped outputs. Note, however, that some bounds such as the maximum value of a call of spot are not tight.

Returns:
resultnp.ndarray | float | PrettyValueObject[str, np.ndarray|float]

If only one what was requested, this function returns a float or np.ndarray for whatever calculation was requested.

If several what were requested, this function returns a PrettyObject with the requested outputs as attributes, named and in order:

  • price

  • delta

  • dk

  • vega

  • gamma

  • theta (positive!)

  • logK

That means you can access the result as follows:

from cdxcore.bs import bs
price, vega = bs( k=0.8, vol=0.2, sqrtT=1., what=bs.PRICE|bs.VEGA )['price','vega'] 

or in order of “price”, “delta”, “dk”, “gamma”, “vega”, “theta”, “logK” (whenever requested) directly in tuple notation:

price, gamma, vega = bs( k=0.8, vol=0.2, sqrtT=1., what=bs.PRICE|bs.VEGA|bs.GAMMA )
Raises:
input errors: ValueError

If any of the inputs is invalid, e.g. negative strikes or volatilities, or if the “what” bitmask is zero or not a valid combination of flags.

precision errors: FloatingPointError

In case any of the calculated values are outside their theoretical bounds by more than some tolerance. The tolerance level is set by self._eps which can be changed for debugging; however please raise issues of this type to the authors.

delta(k, vol, sqrtT=1.0, is_call=True, *, logK=None, eps=None)[source]#

Compute Black Scholes option deltas in drift-less price domain.

Parameters:
knp.ndarray

Strikes.

volnp.ndarray | float

Volatilities or a single volatility.

sqrtTnp.ndarray | float, default 1

Square-root of time.

is_callnp.ndarray | bool, default True

Whether to compute call (True) or put (False) prices.

logK: np.ndarray | float | None, default ``None``

An optional pre-computed log-strike. If provided, this is used instead of computing log(k) internally.

epsfloat | None, default None

An optional precision tolerance for greek validation checks. If not provided, the default is bs._eps (1E-8).

Returns:
deltanp.ndarray

Black Scholes deltas

dk(k, vol, sqrtT=1.0, is_call=True, *, logK=None, eps=None)[source]#

Compute Black Scholes dk (derivative in k) in drift-less price domain.

Parameters:
knp.ndarray

Strikes.

volnp.ndarray | float

Volatilities or a single volatility.

sqrtTnp.ndarray | float, default 1

Square-root of time.

is_callnp.ndarray | bool, default True

Whether to compute call (True) or put (False) prices.

logK: np.ndarray | float | None, default ``None``

An optional pre-computed log-strike. If provided, this is used instead of computing log(k) internally.

epsfloat | None, default None

An optional precision tolerance for internal price validation checks. If not provided, the default is bs._eps (1E-8).

Returns:
dknp.ndarray

Black Scholes dk (derivative in k)

gamma(k, vol, sqrtT=1.0, is_call=True, *, logK=None, eps=None)[source]#

Compute Black Scholes option gamma in drift-less price domain.

Parameters:
knp.ndarray

Strikes.

volnp.ndarray | float

Volatilities or a single volatility.

sqrtTnp.ndarray | float, default 1

Square-root of time.

is_callnp.ndarray | bool, default True

Whether to compute call (True) or put (False) prices.

logK: np.ndarray | float | None, default ``None``

An optional pre-computed log-strike. If provided, this is used instead of computing log(k) internally.

epsfloat | None, default None

An optional precision tolerance for greek validation checks. If not provided, the default is bs._eps (1E-8).

Returns:
gammanp.ndarray

Black Scholes gammas

implied(k, prices, is_call=True, sqrtT=1.0, *, price_tol=1e-06, vol_min=0.01, vol_max=5.0, default_vol=0.0, mask=None, max_iters=100, eps=1e-10, min_vega=1e-12, ret_only_vols=True, on_exceed_bounds='warn', verbose=<cdxcore.verbose.Context object>)[source]#

Solve for implied volatilities using a robust bisection and Newton-Raphson hybrid method. This function is designed to be run for a large number of potentially unrelated options, for example for a time series of surfaces of options.

This routine:

  1. Assigns max_vol to every option whose price is at or above the option price implied by vol_max, and vol_min to every option whose price is at or below the option price implied by vol_min.

  2. Initializes the search at default_vol if it is an array, or otherwise using the closed-form approximation (20). If default_vol is a float, then it is only used where the approximation fails to yield a value.

  3. Run a hybrid bisection and Newton-Raphson method to find the implied volatilities for the remaining options until the maximum number of itersations, max_iters, is reached or the method has converged in the sense that:

    |price(vol) - market_price| < price_tol
    

By default the function just returns the implied volatilities, or their last best guesses. If ret_only_vols is set to False, then it returns a cdxcore.pretty.PrettyValueObject containing the fitted prices, a boolean area indicating issues, and an error statistics object in the following fields:

  • vols contains the fitted volatilities.

  • fits contains the the prices at the fitted volatilities.

  • prices contains the input prices at those values.

  • failed contains a boolean array indicating which fits did not converge.

  • max_iters: the input max_iters.

  • iters: iterations used.

  • max_err: maximum price error.

  • l1_err: average price error.

  • l2_err: quadratic average price error.

Parameters:
knp.ndarray

Strikes.

pricesnp.ndarray

Prices in the same shape as k.

is_callnp.ndarray | bool, default True

Boolean or boolean array of shape compatible with k.

sqrtTnp.ndarray | float, default 1

Square-root of time as float or array compatible with k.

price_tolnp.ndarray | float, default 1E-6

Price tolerance (typically a fraction of spreads) as float or array or array compatible with k. A standard value is 0.1*spread.

vol_minfloat, default 0.01

Minimum volatility.

vol_maxfloat, default 5

Maximum volatility.

default_volnp.ndarray | float, default 0.

Default and initial volatility guess as float or array compatible with k. See description above. Also note that default_vol will be clipped to the range [vol_min, vol_max].

masknp.ndarray | None, default None

A numpy array boolean mask to indicate which options to process. The implied vol of options excluded by mask will be set to default_vol.

max_itersint, default 100

Maximum iterations. Usually the routine uses very few iterations.

epsfloat, default 1E-10

Only used to decide whether stirke or sqrtVar are zero.

min_vegafloat, default 1E-12

Minimum vega for taking an updates step.

ret_only_volsbool, default True

Return only vols.

on_exceed_boundserror | warn | quiet | None, default warn

What to do if the input data violate basic option price bounds such as intrinsic from below or unit/strike from above, respectively. None is equivalent to quiet.

verbosecdxcore.verbose.Context, default cdxcore.verbose.Context.quiet

For printing progress information.

Returns:
resultsnp.ndarray | PrettyValueObject

See above.

price(k, vol, sqrtT=1.0, is_call=True, *, logK=None, eps=None)[source]#

Compute Black Scholes option prices in drift-less price domain.

Parameters:
knp.ndarray

Strikes.

volnp.ndarray | float

Volatilities or a single volatility.

sqrtTnp.ndarray | float, default 1

Square-root of time.

is_callnp.ndarray | bool, default True

Whether to compute call (True) or put (False) prices.

logK: np.ndarray | float | None, default ``None``

An optional pre-computed log-strike. If provided, this is used instead of computing log(k) internally.

epsfloat | None, default None

An optional precision tolerance for internal price validation checks. If not provided, the default is bs._eps (1E-8).

Returns:
pricenp.ndarray

Black Scholes prices

static pure_to_cash(pure, *, fwd, df)[source]#

Converts dictionary of pure price and greeks into market price and greeks.

Assume Cp(T,k) = E[(X_T-k)^+] where X is a pure log-normal martingale. Let C(T,K) := DF F Cp(T, K/F) be the market call price.

Then: `python Delta     = dC/dK = DF Delta^p Gamma     = d^2C/dK^2 = DF Gamma^p / F Vega      = dC/dsigma = DF F Vega^p Opt_Theta = dC/dt = DF F Theta^p (this is optionality theta, excluding curve and carry) DK        = dC/dF = DF DK^p `

Note that this function computes “optionality theta” as the decay due to loss of optionality, and excludes the effect on a change in discount factor or forward.

Parameters:
pureMapping

A dictionary containing any of the greeks above.

fwdfloat

Forward price.

dffloat

Discount factor.

Returns:
mktcdxcore.pretty.PrettyValueObject

A dictionary with the same inputs as pure; if theta was present in pure, then this object will contain opt_theta.

theta(k, vol, sqrtT=1.0, is_call=True, *, logK=None, eps=None)[source]#

Compute Black Scholes option theta in drift-less price domain.

Parameters:
knp.ndarray

Strikes.

volnp.ndarray | float

Volatilities or a single volatility.

sqrtTnp.ndarray | float, default 1

Square-root of time.

is_callnp.ndarray | bool, default True

Whether to compute call (True) or put (False) prices.

logK: np.ndarray | float | None, default ``None``

An optional pre-computed log-strike. If provided, this is used instead of computing log(k) internally.

epsfloat | None, default None

An optional precision tolerance for greek validation checks. If not provided, the default is bs._eps (1E-8).

Returns:
thetanp.ndarray

Black Scholes thetas

vega(k, vol, sqrtT=1.0, is_call=True, *, logK=None, eps=None)[source]#

Compute Black Scholes option vega in drift-less price domain.

Parameters:
knp.ndarray

Strikes.

volnp.ndarray | float

Volatilities or a single volatility.

sqrtTnp.ndarray | float, default 1

Square-root of time.

is_callnp.ndarray | bool, default True

Whether to compute call (True) or put (False) prices.

logK: np.ndarray | float | None, default ``None``

An optional pre-computed log-strike. If provided, this is used instead of computing log(k) internally.

epsfloat | None, default None

An optional precision tolerance for greek validation checks. If not provided, the default is bs._eps (1E-8).

Returns:
veganp.ndarray

Black Scholes vegas

class cdxcore.bs.BSFLAGS(*values)[source]#

Bases: IntFlag

DELTA = 2#

Flag to request Delta: N1 for a call

DK = 4#

Flag to request dK: -N2 for a call

GAMMA = 8#

Flag to request Gamma

LOGK = 64#

Flag to request logK, set to 1 where k=0 or vol*sqrtT=0

PRICE = 1#

Flag to request Price, for a call: N1 - k N2

THETA = 32#

Flag to request Theta

VEGA = 16#

Flag to request Vega

cdxcore.bs.bs = <cdxcore.bs.BS object>#

Main instance of the cdxcore.bs.BS class.

This is synthatic sugar for being able to write:

from cdxcore.bs import bs
price, vega = bs( k, vol, sqrtT, is_call, what=BS.PRICE|BS.VEGA )

or:

gamma = bs.gamma( k, vol, sqrtT, is_call )