Source code for shapley_numba.examples.finance

"""Games and computations related to finance."""

import math

import numba
import numpy as np

from shapley_numba import numba_game
from shapley_numba.game_templates import (
    ParameterChangeExplanation,
    parameter_change_explanation_spec,
)

__all__ = [
    'BlackScholesCallGame',
]


@numba.jit(nopython=True)
def norm_cdf(x: float | int) -> float:
    """Compute norm cdf.

    The cumulative distribution function of the standard normal distribution.
    """
    return 0.5 * (1.0 + math.erf(x / math.sqrt(2.0)))


@numba.jit(nopython=True)
def black_scholes_call_price(params: list[float], strike: float) -> float:  # noqa: D417
    """Calculate the Black-Scholes call option price from a parameter array.

    Parameters
    ----------
    params : numpy.ndarray
        A 1D array containing the Black-Scholes parameters in the following order:
        [S, T, r, sigma]
        S : float - Spot price of the underlying asset.
        T : float - Time to expiration (in years).
        r : float - Risk-free interest rate (annualized).
        sigma : float - Volatility of the underlying asset (annualized).
        strike : float - Strike price of the option.

    Returns
    -------
    float
        The Black-Scholes call option price.

    """
    S, T, r, sigma = params[0], params[1], params[2], params[3]  # noqa: N806
    K = strike  # noqa: N806
    d1 = (np.log(S / K) + (r + 0.5 * sigma**2) * T) / (sigma * np.sqrt(T))
    d2 = d1 - sigma * np.sqrt(T)
    call = S * norm_cdf(d1) - K * np.exp(-r * T) * norm_cdf(d2)
    return float(call)


black_scholes_call_price_game_spec = [
    ('strike', numba.float64)
] + parameter_change_explanation_spec


[docs] @numba_game(black_scholes_call_price_game_spec) class BlackScholesCallGame(ParameterChangeExplanation): """A game representing the Black-Scholes call option price.""" def __init__(self, old_parameters, new_parameters, strike: float): """Initialize the BlackScholesCallGame.""" self.strike = strike # Unfortunately super doesn't work in jitclasses # super().__init__(old_parameters, new_parameters) self.old_parameters = old_parameters self.new_parameters = new_parameters def model_evaluate(self, params: list[float]) -> float: """Evaluate the Black-Scholes call option price with the given parameters.""" return float(black_scholes_call_price(params, self.strike))
@numba.jit(nopython=True) def black_scholes_put_price(params: list[float]): """Calculate the Black-Scholes put option price from a parameter array. Parameters ---------- params : numpy.ndarray A 1D array containing the Black-Scholes parameters in the following order: [S, K, T, r, sigma] S : float - Spot price of the underlying asset. K : float - Strike price of the option. T : float - Time to expiration (in years). r : float - Risk-free interest rate (annualized). sigma : float - Volatility of the underlying asset (annualized). Returns ------- float The Black-Scholes put option price. """ S, K, T, r, sigma = params[0], params[1], params[2], params[3], params[4] # noqa: N806 d1 = (np.log(S / K) + (r + 0.5 * sigma**2) * T) / (sigma * np.sqrt(T)) d2 = d1 - sigma * np.sqrt(T) put = K * np.exp(-r * T) * norm_cdf(-d2) - S * norm_cdf(-d1) return put