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

VEGA = 16#

Flag to request Vega

__call__(k, vol, sqrtT=1.0, what=<BSFLAGS.PRICE: 1>, is_call=True, *, logK=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

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

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

  • 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 reauested) directly in tuple notation:

price, gamma, vega = bs( k=0.8, vol=0.2, sqrtT=1., what=bs.PRICE|bs.VEGA|bs.GAMMA )
delta(k, vol, sqrtT=1.0, is_call=True, *, logK=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.

Returns:
deltanp.ndarray

Black Scholes deltas

dk(k, vol, sqrtT=1.0, is_call=True, *, logK=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.

Returns:
dknp.ndarray

Black Scholes dk (derivative in k)

gamma(k, vol, sqrtT=1.0, is_call=True, *, logK=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.

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)[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.

Returns:
pricenp.ndarray

Black Scholes prices

theta(k, vol, sqrtT=1.0, is_call=True, *, logK=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.

Returns:
thetanp.ndarray

Black Scholes thetas

vega(k, vol, sqrtT=1.0, is_call=True, *, logK=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.

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 )