def load_data(self, tickers=None, start_date=history_default_start_date, end_date=dt.datetime.today()): data_1 = get_equity_implied_volatility(tickers=tickers, fields=self.ivol_fields, start_date=start_date, end_date=end_date) self.data = self.data.append(data_1).drop_duplicates() dates = self.data.index.get_level_values('date') for i in range(1, 5): col1 = 'maturity_date_' + str(i) + 'mc' col2 = 'days_to_maturity_' + str(i) + 'mc' self.data[col1] = \ utils.calendarday(date=dates, num_days=self.data[col2]) self.data = self.data.sort_index().drop_duplicates() self.data = self.get_maturity_dates(tickers=tickers) # enforced cleaning self.data['curvature'] = utils.numeric_cap_floor( x=self.data['curvature'], cap=1.0) self.data['curvature_inf'] = utils.numeric_cap_floor( x=self.data['curvature_inf'], cap=1.0)
def black_scholes_d1(spot=None, strike=None, tenor_in_days=None, risk_free=None, ivol=None, div=None, div_type='yield'): eps = 1e-7 t = utils.numeric_cap_floor(x=tenor_in_days / constants.trading_days_per_year, floor=eps) pv_div, adj_spot = _handle_dividend(div=div, div_type=div_type, risk_free=risk_free, spot=spot, tenor_in_days=tenor_in_days) d1 = (np.log(adj_spot / strike) + (risk_free + ivol ** 2.0 / 2.0) * t) \ / (ivol * t ** 0.5) d2 = d1 - ivol * t**0.5 d1 = pd.to_numeric(d1) d2 = pd.to_numeric(d2) return d1, d2
def black_scholes_vega(spot=None, strike=None, tenor_in_days=None, risk_free=None, ivol=None, div=0.0, div_type='yield', d1=None, option_type=None): eps = 1e-7 t = utils.numeric_cap_floor(x=tenor_in_days / constants.trading_days_per_year, floor=eps) if div_type == 'yield': div_yield = div elif div_type == 'amount': div_yield = div / spot / t if d1 is None: d1, d2 = black_scholes_d1(spot=spot, strike=strike, tenor_in_days=tenor_in_days, risk_free=risk_free, ivol=ivol, div=div, div_type=div_type) vega = spot * np.exp(-div_yield * t) * norm.pdf(d1) * np.sqrt(t) / 100.0 return vega
def put_call_parity(input_price=None, option_type='c', spot=None, strike=None, div=0.0, div_type='yield', risk_free=None, tenor_in_days=None): eps = 1e-7 t = utils.numeric_cap_floor(x=tenor_in_days / constants.trading_days_per_year, floor=eps) pv_div, adj_spot = _handle_dividend(div=div, div_type=div_type, risk_free=risk_free, spot=spot, tenor_in_days=tenor_in_days) # cash plus call plus divs equals put plus stock if option_type.upper() in (['C', 'CALL']): other_price = input_price + spot \ - np.exp(risk_free * t) * strike - pv_div elif option_type.upper() in (['P', 'PUT']): other_price = np.exp(risk_free * t) * strike + pv_div - spot return other_price
def black_scholes_price(spot=None, strike=None, tenor_in_days=None, risk_free=None, ivol=None, div=0.0, div_type='yield', d1=None, option_type='c'): eps = 1e-7 t = utils.numeric_cap_floor(x=tenor_in_days / constants.trading_days_per_year, floor=eps) pv_div, adj_spot = _handle_dividend(div=div, div_type=div_type, risk_free=risk_free, spot=spot, tenor_in_days=tenor_in_days) if d1 is None: d1, d2 = black_scholes_d1(spot=spot, strike=strike, tenor_in_days=tenor_in_days, risk_free=risk_free, ivol=ivol, div=div, div_type=div_type) if isinstance(option_type, pd.Series): price = pd.Series(index=option_type.index) option_type = option_type.str.upper() d1 = pd.to_numeric(d1) d2 = pd.to_numeric(d2) call_ind = option_type.index[option_type.isin(['C', 'CALL'])] price.loc[call_ind] = adj_spot.loc[call_ind] \ * norm.cdf(d1.loc[call_ind]) \ - np.exp(-risk_free.loc[call_ind] * t.loc[call_ind])\ * strike.loc[call_ind] * norm.cdf(d2.loc[call_ind]) put_ind = option_type.index[option_type.isin(['P', 'PUT'])] price.loc[put_ind] = strike.loc[put_ind] \ * np.exp(-risk_free.loc[put_ind] * t.loc[put_ind]) * norm.cdf(-d2.loc[put_ind]) \ - spot.loc[put_ind] * norm.cdf(-d1.loc[put_ind]) else: if option_type.upper() in (['C', 'CALL']): price = adj_spot * norm.cdf(d1) \ - np.exp(-risk_free * t) * strike * norm.cdf(d2) elif option_type.upper() in (['P', 'PUT']): price = strike * np.exp(-risk_free * t) * norm.cdf(-d2) \ - spot * norm.cdf(-d1) return price
def black_scholes_delta(spot=None, strike=None, tenor_in_days=None, risk_free=None, ivol=None, div=0.0, div_type='yield', d1=None, option_type=None): eps = 1e-7 t = utils.numeric_cap_floor(x=tenor_in_days / constants.trading_days_per_year, floor=eps) pv_div, adj_spot = _handle_dividend(div=div, div_type=div_type, risk_free=risk_free, spot=spot, tenor_in_days=tenor_in_days) div_yield = pv_div / spot / t if d1 is None: d1, d2 = black_scholes_d1(spot=spot, strike=strike, tenor_in_days=tenor_in_days, risk_free=risk_free, ivol=ivol, div=div, div_type=div_type) if isinstance(option_type, pd.Series): delta = pd.Series(index=option_type.index) option_type = option_type.str.upper() call_ind = option_type.index[option_type.isin(['C', 'CALL'])] delta.loc[call_ind] = np.exp( -div_yield.loc[call_ind] * t.loc[call_ind]) * norm.cdf( d1.loc[call_ind]) put_ind = option_type.index[option_type.isin(['P', 'PUT'])] delta.loc[put_ind] = -np.exp(-div_yield.loc[put_ind] * t.loc[put_ind] ) * norm.cdf(-d1.loc[put_ind]) else: if option_type.upper() in (['C', 'CALL']): delta = np.exp(-div_yield * t) * norm.cdf(d1) elif option_type.upper() in (['P', 'PUT']): delta = -np.exp(-div_yield * t) * norm.cdf(-d1) return delta
def _handle_dividend(div=0.0, div_type='yield', risk_free=0.0, spot=None, tenor_in_days=None): eps = 1e-7 t = utils.numeric_cap_floor(x=tenor_in_days / constants.trading_days_per_year, floor=eps) if div_type == 'yield': pv_div = (1.0 - np.exp(-risk_free * t)) / (eps + risk_free) * div * t elif div_type == 'amount': pv_div = np.exp(-t * risk_free) * div else: pv_div = 0.0 adj_spot = spot - pv_div return pv_div, adj_spot
def black_scholes_moneyness_from_delta(call_delta=None, tenor_in_days=None, ivol=None, risk_free=None, div_yield=None): """ :param call_delta: this can be a scalar or an array-like. If the latter, ivol needs to have call delta as its columns :param tenor_in_days: this can be an integer or a Series with index that matches ivol's index :param ivol: this can be a scalar, a series, or a DataFrame indexed on date and/or ticker, with columns = call delta :param risk_free: can be scalar or series :param div_yield: can be scalar or series :return: scalar, series or DataFrame depending on inputs """ if isinstance(call_delta, list): call_delta = np.array(call_delta) put_delta = 1.0 - call_delta eps = 1e-7 t = utils.numeric_cap_floor(x=tenor_in_days / constants.trading_days_per_year, floor=eps) if isinstance(tenor_in_days, pd.Series) and isinstance(ivol, pd.DataFrame): m = pd.DataFrame(index=ivol.index, columns=ivol.columns) for col in ivol.columns: m[col] = np.exp(ivol[col] * t**0.5 * norm.ppf((1 - col) * np.exp(div_yield * t)) - (risk_free - div_yield - ivol[col]**2.0 / 2.0) * t) else: m = np.exp(ivol * t**0.5 * norm.ppf(put_delta * np.exp(div_yield * t)) - (risk_free - div_yield - ivol**2.0 / 2.0) * t) return m