Source code for putcall.optionvaluator

# -*- coding: utf-8 -*-

# putcall
# -------
# Collection of classical option pricing formulas.
# 
# Author:   sonntagsgesicht, based on a fork of Deutsche Postbank [pbrisk]
# Version:  0.2, copyright Wednesday, 18 September 2019
# Website:  https://github.com/sonntagsgesicht/putcall
# License:  Apache License 2.0 (see LICENSE file)


from .formulas import option_payoff, digital_option_payoff, straddle_payoff

from .formulas import black, black_delta, black_gamma, black_vega
from .formulas import black_digital, black_digital_delta, black_digital_gamma, black_digital_vega
from .formulas import black_straddle, black_straddle_delta, black_straddle_gamma, black_straddle_vega

from .formulas import bachelier, bachelier_delta, bachelier_gamma, bachelier_vega
from .formulas import bachelier_digital, bachelier_digital_delta, bachelier_digital_gamma, bachelier_digital_vega
from .formulas import bachelier_straddle, bachelier_straddle_delta, bachelier_straddle_gamma, bachelier_straddle_vega

from .calibration import OptionValueByVolatility, ImpliedVolCalculator


[docs]class OptionType(object): CALL, PUT, DIGITAL_CALL, DIGITAL_PUT, STRADDLE = list(range(5))
[docs]class OptionValuator(object): def __init__(self, delta=None, vega=None): self._delta = self._parse(delta, (0.00001, 1.0, True)) self._analytical_delta = delta is None self._vega = self._parse(vega, (0.0001, 1.0, True)) self._analytical_vega = vega is None @staticmethod def _parse(arg, default=tuple()): # fill list entries by arg entries if arg is None: return default arg = [arg] if not isinstance(arg, (list, tuple)) else arg ret = list(default) for i in range(len(default)): ret[i] = arg[i] if i < len(arg) else default[i] return tuple(ret) # --- pricing ---
[docs] def option_value(self, forward, strike, time, volatility, option_type, discount_factor=1.0): return discount_factor * self._option_value(forward, strike, time, volatility, option_type)
def _option_value(self, forward, strike, time, volatility, option_type): raise NotImplementedError # --- solving ---
[docs] def implied_vol(self, forward, strike, time, price, option_type, discount_factor=1.0): option_val = OptionValueByVolatility(self.option_value, forward, strike, time, option_type, discount_factor) implied_vol_calculator = ImpliedVolCalculator() impl_vol = implied_vol_calculator.implied_vol(price, option_val, 0.15, 0.03) return impl_vol
# --- delta risk ---
[docs] def delta(self, forward, strike, time, volatility, option_type, discount_factor=1.0): risk = None if self._analytical_delta: risk = self._analytic_delta(forward, strike, time, volatility, option_type) if risk is None: risk = self._bump_delta(forward, strike, time, volatility, option_type) return discount_factor * risk
def _analytic_delta(self, forward, strike, time, volatility, option_type): return None def _bump_delta(self, forward, strike, time, volatility, option_type): f = (lambda x: self.option_value(x, strike, time, volatility, option_type)) shift, quote, shift_abs = self._delta forward_shift = shift if shift_abs else forward * shift return quote * (f(forward + forward_shift) - f(forward)) / shift # --- gamma risk ---
[docs] def gamma(self, forward, strike, time, volatility, option_type, discount_factor=1.0): risk = None if self._analytical_delta: risk = self._analytic_gamma(forward, strike, time, volatility, option_type) if risk is None: risk = self._bump_gamma(forward, strike, time, volatility, option_type) return discount_factor * risk
def _analytic_gamma(self, forward, strike, time, volatility, option_type): return None def _bump_gamma(self, forward, strike, time, volatility, option_type): f = (lambda x: self._bump_delta(x, strike, time, volatility, option_type)) shift, quote, shift_abs = self._delta forward_shift = shift if shift_abs else forward / shift return quote * (f(forward) - f(forward - forward_shift)) / shift # --- vega risk ---
[docs] def vega(self, forward, strike, time, volatility, option_type, discount_factor=1.0): risk = None if self._analytical_vega: risk = self._analytic_vega(forward, strike, time, volatility, option_type) if risk is None: risk = self._bump_vega(forward, strike, time, volatility, option_type) return discount_factor * risk
def _analytic_vega(self, forward, strike, time, volatility, option_type): return None def _bump_vega(self, forward, strike, time, volatility, option_type): f = (lambda x: self.option_value(forward, strike, time, x, option_type)) shift, quote, shift_abs = self._vega volatility_shift = shift if shift_abs else volatility * shift return quote * (f(volatility + volatility_shift) - f(volatility)) / shift
[docs]class OptionValuatorIntrinsic(OptionValuator): def _option_value(self, forward, strike, time, volatility, option_type, discount_factor=1.0): if option_type == OptionType.CALL: result = option_payoff(forward, strike, True) elif option_type == OptionType.PUT: result = option_payoff(forward, strike, False) elif option_type == OptionType.DIGITAL_CALL: result = digital_option_payoff(forward, strike, True) elif option_type == OptionType.DIGITAL_CALL: result = digital_option_payoff(forward, strike, False) elif option_type == OptionType.STRADDLE: result = straddle_payoff(forward, strike) else: raise Exception('Unknown OptionType ' + str(option_type) + ' ' + __name__) return discount_factor * result
[docs] def implied_vol(self, forward, strike, time, price, option_type, discount_factor=1.0): return 0.0
[docs]class OptionValuatorN(OptionValuator): def _option_value(self, forward, strike, time, volatility, option_type): if option_type == OptionType.CALL: return bachelier(forward, strike, volatility, time, True) if option_type == OptionType.PUT: return bachelier(forward, strike, volatility, time, False) if option_type == OptionType.DIGITAL_CALL: return bachelier_digital(forward, strike, volatility, time, True) if option_type == OptionType.DIGITAL_PUT: return bachelier_digital(forward, strike, volatility, time, False) if option_type == OptionType.STRADDLE: return bachelier_straddle(forward, strike, volatility, time, False) return None def _analytic_delta(self, forward, strike, time, volatility, option_type): if option_type == OptionType.CALL: return bachelier_delta(forward, strike, volatility, time, True) if option_type == OptionType.PUT: return bachelier_delta(forward, strike, volatility, time, False) if option_type == OptionType.DIGITAL_CALL: return bachelier_digital_delta(forward, strike, volatility, time, True) if option_type == OptionType.DIGITAL_PUT: return bachelier_digital_delta(forward, strike, volatility, time, False) if option_type == OptionType.STRADDLE: return bachelier_straddle_delta(forward, strike, volatility, time, False) return None def _analytic_gamma(self, forward, strike, time, volatility, option_type): if option_type == OptionType.CALL: return bachelier_gamma(forward, strike, volatility, time, True) if option_type == OptionType.PUT: return bachelier_gamma(forward, strike, volatility, time, False) if option_type == OptionType.DIGITAL_CALL: return bachelier_digital_gamma(forward, strike, volatility, time, True) if option_type == OptionType.DIGITAL_PUT: return bachelier_digital_gamma(forward, strike, volatility, time, False) if option_type == OptionType.STRADDLE: return bachelier_straddle_gamma(forward, strike, volatility, time, False) return None def _analytic_vega(self, forward, strike, time, volatility, option_type): if option_type == OptionType.CALL: return bachelier_vega(forward, strike, volatility, time, True) if option_type == OptionType.PUT: return bachelier_vega(forward, strike, volatility, time, False) if option_type == OptionType.DIGITAL_CALL: return bachelier_digital_vega(forward, strike, volatility, time, True) if option_type == OptionType.DIGITAL_PUT: return bachelier_digital_vega(forward, strike, volatility, time, False) if option_type == OptionType.STRADDLE: return bachelier_straddle_vega(forward, strike, volatility, time, False) return None
[docs]class OptionValuatorLN(OptionValuator): def _option_value(self, forward, strike, time, volatility, option_type, ): if option_type == OptionType.CALL: return black(forward, strike, volatility, time, True) if option_type == OptionType.PUT: return black(forward, strike, volatility, time, False) if option_type == OptionType.DIGITAL_CALL: return black_digital(forward, strike, volatility, time, True) if option_type == OptionType.DIGITAL_PUT: return black_digital(forward, strike, volatility, time, False) if option_type == OptionType.STRADDLE: return black_straddle(forward, strike, volatility, time, False) return None def _analytic_delta(self, forward, strike, time, volatility, option_type): if option_type == OptionType.CALL: return black_delta(forward, strike, volatility, time, True) if option_type == OptionType.PUT: return black_delta(forward, strike, volatility, time, False) if option_type == OptionType.DIGITAL_CALL: return black_digital_delta(forward, strike, volatility, time, True) if option_type == OptionType.DIGITAL_PUT: return black_digital_delta(forward, strike, volatility, time, False) if option_type == OptionType.STRADDLE: return black_straddle_delta(forward, strike, volatility, time, False) return None def _analytic_gamma(self, forward, strike, time, volatility, option_type): if option_type == OptionType.CALL: return black_gamma(forward, strike, volatility, time, True) if option_type == OptionType.PUT: return black_gamma(forward, strike, volatility, time, False) if option_type == OptionType.DIGITAL_CALL: return black_digital_gamma(forward, strike, volatility, time, True) if option_type == OptionType.DIGITAL_PUT: return black_digital_gamma(forward, strike, volatility, time, False) if option_type == OptionType.STRADDLE: return black_straddle_gamma(forward, strike, volatility, time, False) return None def _analytic_vega(self, forward, strike, time, volatility, option_type): if option_type == OptionType.CALL: return black_vega(forward, strike, volatility, time, True) if option_type == OptionType.PUT: return black_vega(forward, strike, volatility, time, False) if option_type == OptionType.DIGITAL_CALL: return black_digital_vega(forward, strike, volatility, time, True) if option_type == OptionType.DIGITAL_PUT: return black_digital_vega(forward, strike, volatility, time, False) if option_type == OptionType.STRADDLE: return black_straddle_vega(forward, strike, volatility, time, False) return None
[docs]class OptionValuatorSLN(OptionValuator): def __init__(self, displacement=0.03, delta=None, vega=None): super(OptionValuatorSLN, self).__init__(delta, vega) self.displacement = displacement self._option_valuatorLN = OptionValuatorLN() def _get_shifted_forward_and_strike(self, forward, strike): return forward + self.displacement, strike + self.displacement def _option_value(self, forward, strike, time, volatility, optionType): fwd, k = self._get_shifted_forward_and_strike(forward, strike) return self._option_valuatorLN._option_value(fwd, k, time, volatility, optionType)
[docs] def implied_vol(self, forward, strike, time, price, optionType, discount_factor=1.0): fwd, k = self._get_shifted_forward_and_strike(forward, strike) return self._option_valuatorLN.implied_vol(fwd, k, time, price, optionType, discount_factor)
def _analytic_vega(self, forward, strike, time, volatility, option_type): fwd, k = self._get_shifted_forward_and_strike(forward, strike) return self._option_valuatorLN._analytic_vega(fwd, k, time, volatility, option_type) def _analytic_gamma(self, forward, strike, time, volatility, option_type): fwd, k = self._get_shifted_forward_and_strike(forward, strike) return self._option_valuatorLN._analytic_gamma(fwd, k, time, volatility, option_type) def _analytic_delta(self, forward, strike, time, volatility, option_type): fwd, k = self._get_shifted_forward_and_strike(forward, strike) return self._option_valuatorLN._analytic_delta(fwd, k, time, volatility, option_type)