diff --git a/financepy/market/volatility/fx_vol_surface.py b/financepy/market/volatility/fx_vol_surface.py index 6cf168a6..f1de4af8 100644 --- a/financepy/market/volatility/fx_vol_surface.py +++ b/financepy/market/volatility/fx_vol_surface.py @@ -101,7 +101,7 @@ def obj_fast(params, *args): sigma_k_25d_c_ms = vol_function(vol_type_value, params, f, k_25d_c_ms, t) - V_25d_C_MS = bs_value( + v_25d_c_ms = bs_value( s, t, k_25d_c_ms, @@ -113,7 +113,7 @@ def obj_fast(params, *args): sigma_k_25d_p_ms = vol_function(vol_type_value, params, f, k_25d_p_ms, t) - V_25d_P_MS = bs_value( + v_25d_p_ms = bs_value( s, t, k_25d_p_ms, @@ -123,7 +123,7 @@ def obj_fast(params, *args): OptionTypes.EUROPEAN_PUT.value, ) - v_25d_ms = V_25d_C_MS + V_25d_P_MS + v_25d_ms = v_25d_c_ms + v_25d_p_ms term2 = (v_25d_ms - v_25d_ms_target) ** 2 ########################################################################### @@ -218,16 +218,16 @@ def solve_to_horizon_fast( ) # USE MARKET STRANGLE VOL TO DETERMINE PRICE OF A MARKET STRANGLE - V_25d_C_MS = bs_value( + v_25d_c_ms = bs_value( s, t, k_25d_c_ms, rd, rf, vol_25d_ms, OptionTypes.EUROPEAN_CALL.value ) - V_25d_P_MS = bs_value( + v_25d_p_ms = bs_value( s, t, k_25d_p_ms, rd, rf, vol_25d_ms, OptionTypes.EUROPEAN_PUT.value ) # Market price of strangle in the domestic currency - v_25d_ms = V_25d_C_MS + V_25d_P_MS + v_25d_ms = v_25d_c_ms + v_25d_p_ms # Determine parameters of vol surface using minimisation tol = 1e-8 @@ -450,11 +450,13 @@ def solve_for_strike( else: phi = -1.0 - F0T = spot_fx_rate * for_df / dom_df + fwd_fx_rate = spot_fx_rate * for_df / dom_df vsqrtt = volatility * np.sqrt(t_del) arg = delta_target * phi / for_df # CHECK THIS !!! norm_inv_delta = norminvcdf(arg) - K = F0T * np.exp(-vsqrtt * (phi * norm_inv_delta - vsqrtt / 2.0)) + K = fwd_fx_rate * np.exp( + -vsqrtt * (phi * norm_inv_delta - vsqrtt / 2.0) + ) return K elif delta_method_value == FinFXDeltaMethod.FORWARD_DELTA.value: @@ -467,11 +469,13 @@ def solve_for_strike( else: phi = -1.0 - F0T = spot_fx_rate * for_df / dom_df + fwd_fx_rate = spot_fx_rate * for_df / dom_df vsqrtt = volatility * np.sqrt(t_del) arg = delta_target * phi # CHECK THIS!!!!!!!! norm_inv_delta = norminvcdf(arg) - K = F0T * np.exp(-vsqrtt * (phi * norm_inv_delta - vsqrtt / 2.0)) + K = fwd_fx_rate * np.exp( + -vsqrtt * (phi * norm_inv_delta - vsqrtt / 2.0) + ) return K elif delta_method_value == FinFXDeltaMethod.SPOT_DELTA_PREM_ADJ.value: @@ -537,8 +541,8 @@ def __init__( foreign_curve: DiscountCurve, tenors: list, atm_vols: (list, np.ndarray), - ms25deltaVols: (list, np.ndarray), - rr25deltaVols: (list, np.ndarray), + ms_25_delta_vols: (list, np.ndarray), + rr_25_delta_vols: (list, np.ndarray), atm_method: FinFXATMMethod = FinFXATMMethod.FWD_DELTA_NEUTRAL, delta_method: FinFXDeltaMethod = FinFXDeltaMethod.SPOT_DELTA, vol_func_type: VolFuncTypes = VolFuncTypes.CLARK, @@ -569,16 +573,16 @@ def __init__( if len(atm_vols) != self.num_vol_curves: raise FinError("Number ATM vols must equal number of tenors") - if len(ms25deltaVols) != self.num_vol_curves: + if len(ms_25_delta_vols) != self.num_vol_curves: raise FinError("Number MS25d vols must equal number of tenors") - if len(rr25deltaVols) != self.num_vol_curves: + if len(rr_25_delta_vols) != self.num_vol_curves: raise FinError("Number RR25d vols must equal number of tenors") self.tenors = tenors self.atm_vols = np.array(atm_vols) / 100.0 - self.ms25deltaVols = np.array(ms25deltaVols) / 100.0 - self.rr25deltaVols = np.array(rr25deltaVols) / 100.0 + self.ms_25_delta_vols = np.array(ms_25_delta_vols) / 100.0 + self.rr_25_delta_vols = np.array(rr_25_delta_vols) / 100.0 self.atm_method = atm_method self.delta_method = delta_method @@ -770,8 +774,8 @@ def build_vol_surface(self): for i in range(0, num_vol_curves): atm_vol = self.atm_vols[i] - ms25 = self.ms25deltaVols[i] - rr25 = self.rr25deltaVols[i] + ms25 = self.ms_25_delta_vols[i] + rr25 = self.rr_25_delta_vols[i] s25 = atm_vol + ms25 + rr25 / 2.0 s50 = atm_vol s75 = atm_vol + ms25 - rr25 / 2.0 @@ -840,8 +844,8 @@ def build_vol_surface(self): r_f = self.rf[i] k_atm = self.k_atm[i] atm_vol = self.atm_vols[i] - ms_25d_vol = self.ms25deltaVols[i] - rr_25d_vol = self.rr25deltaVols[i] + ms_25d_vol = self.ms_25_delta_vols[i] + rr_25d_vol = self.rr_25_delta_vols[i] # print(t, rd, rf, k_atm, atm_vol, ms_25d_vol, rr_25d_vol) @@ -939,11 +943,11 @@ def check_calibration(self, verbose: bool, tol: float = 1e-6): print("IN ATM VOL: %9.6f %%" % (100.0 * self.atm_vols[i])) print( "IN MKT STRANGLE 25d VOL: %9.6f %%" - % (100.0 * self.ms25deltaVols[i]) + % (100.0 * self.ms_25_delta_vols[i]) ) print( "IN RSK REVERSAL 25d VOL: %9.6f %%" - % (100.0 * self.rr25deltaVols[i]) + % (100.0 * self.rr_25_delta_vols[i]) ) call = FXVanillaOption( @@ -1036,14 +1040,14 @@ def check_calibration(self, verbose: bool, tol: float = 1e-6): # THESE STRIKES ARE DETERMINED BY SETTING DELTA TO 0.25/-0.25 ################################################################### - ms_vol = self.atm_vols[i] + self.ms25deltaVols[i] + ms_vol = self.atm_vols[i] + self.ms_25_delta_vols[i] if verbose: print("======================================================") print( "MKT STRANGLE VOL IN: %9.6f %%" - % (100.0 * self.ms25deltaVols[i]) + % (100.0 * self.ms_25_delta_vols[i]) ) call.strike_fx_rate = self.k_25d_c_ms[i] @@ -1254,19 +1258,19 @@ def check_calibration(self, verbose: bool, tol: float = 1e-6): ) print( "RR = VOL_K_25_C - VOL_K_25_P => RR_IN: %9.6f %% RR_OUT: %9.6f %%" - % (100.0 * self.rr25deltaVols[i], 100.0 * sigma_RR) + % (100.0 * self.rr_25_delta_vols[i], 100.0 * sigma_RR) ) print( "=========================================================" ) - diff = sigma_RR - self.rr25deltaVols[i] + diff = sigma_RR - self.rr_25_delta_vols[i] if np.abs(diff) > tol: print( "FAILED FIT TO 25d RRV IN: % 9.6f OUT: % 9.6f DIFF: % 9.6f" % ( - self.rr25deltaVols[i] * 100.0, + self.rr_25_delta_vols[i] * 100.0, sigma_RR * 100.0, diff * 100.0, ) @@ -1285,7 +1289,7 @@ def implied_dbns(self, low_fx, high_fx, num_intervals): f = self.fwd[i_tenor] t_exp = self.t_exp[i_tenor] - dFX = (high_fx - low_fx) / num_intervals + d_fx = (high_fx - low_fx) / num_intervals dom_df = self.domestic_curve._df(t_exp) for_df = self.foreign_curve._df(t_exp) @@ -1296,9 +1300,9 @@ def implied_dbns(self, low_fx, high_fx, num_intervals): Ks = [] vols = [] - for iK in range(0, num_intervals): + for ik in range(0, num_intervals): - k = low_fx + iK * dFX + k = low_fx + ik * d_fx vol = vol_function( self.vol_func_type.value, @@ -1334,8 +1338,8 @@ def plot_vol_curves(self): for tenor_index in range(0, self.num_vol_curves): atm_vol = self.atm_vols[tenor_index] * 100 - ms_vol = self.ms25deltaVols[tenor_index] * 100 - rr_vol = self.rr25deltaVols[tenor_index] * 100 + ms_vol = self.ms_25_delta_vols[tenor_index] * 100 + rr_vol = self.rr_25_delta_vols[tenor_index] * 100 low_k = self.k_25d_p[tenor_index] * 0.75 high_k = self.k_25d_c[tenor_index] * 1.25 @@ -1419,8 +1423,8 @@ def __repr__(self): s += label_to_string("FWD FX", self.fwd[i]) s += label_to_string("ATM VOLS", self.atm_vols[i] * 100.0) - s += label_to_string("MS VOLS", self.ms25deltaVols[i] * 100.0) - s += label_to_string("RR VOLS", self.rr25deltaVols[i] * 100.0) + s += label_to_string("MS VOLS", self.ms_25_delta_vols[i] * 100.0) + s += label_to_string("RR VOLS", self.rr_25_delta_vols[i] * 100.0) s += label_to_string("ATM Strike", self.k_atm[i]) s += label_to_string("ATM Delta", self.delta_atm[i]) diff --git a/financepy/market/volatility/fx_vol_surface_plus.py b/financepy/market/volatility/fx_vol_surface_plus.py index 2b8aaa0a..7e9ac6fd 100644 --- a/financepy/market/volatility/fx_vol_surface_plus.py +++ b/financepy/market/volatility/fx_vol_surface_plus.py @@ -239,8 +239,8 @@ def _obj(params, *args): vol_type_value, params, strikes_null, gaps_null, f, k_25d_p, t ) - sigma_25d_RR = sigma_k_25d_c - sigma_k_25d_p - term_25d_2 = (sigma_25d_RR - target_25d_rr_vol) ** 2 + sigma_25d_rr = sigma_k_25d_c - sigma_k_25d_p + term_25d_2 = (sigma_25d_rr - target_25d_rr_vol) ** 2 else: @@ -331,8 +331,8 @@ def _obj(params, *args): vol_type_value, params, strikes_null, gaps_null, f, k_10d_p, t ) - sigma_10d_RR = sigma_k_10d_c - sigma_k_10d_p - term10d_2 = (sigma_10d_RR - target_10d_rr_vol) ** 2 + sigma_10d_rr = sigma_k_10d_c - sigma_k_10d_p + term10d_2 = (sigma_10d_rr - target_10d_rr_vol) ** 2 else: @@ -485,8 +485,8 @@ def _obj_gap(gaps, *args): print("sigma_k_25d_p", sigma_k_25d_p) - sigma_25d_RR = sigma_k_25d_c - sigma_k_25d_p - term_25d_2 = (sigma_25d_RR - target_25d_rr_vol) ** 2 + sigma_25d_rr = sigma_k_25d_c - sigma_k_25d_p + term_25d_2 = (sigma_25d_rr - target_25d_rr_vol) ** 2 ########################################################################### # Match the market strangle value but this has to be at the MS 10d strikes @@ -576,8 +576,8 @@ def _obj_gap(gaps, *args): print("SIGMA_k_10d_p", sigma_k_10d_p) - sigma_10d_RR = sigma_k_10d_c - sigma_k_10d_p - term10d_2 = (sigma_10d_RR - target_10d_rr_vol) ** 2 + sigma_10d_rr = sigma_k_10d_c - sigma_k_10d_p + term10d_2 = (sigma_10d_rr - target_10d_rr_vol) ** 2 ########################################################################### # Alpha interpolates between fitting only ATM and 25d when alpha = 0.0 and @@ -951,30 +951,35 @@ def vol_function(vol_function_type_value, params, strikes, gaps, f, k, t): # print("vol_function", vol_function_type_value) if len(strikes) == 1: - gapK = 0.0 + gap_k = 0.0 else: - gapK = _interpolate_gap(k, strikes, gaps) + gap_k = _interpolate_gap(k, strikes, gaps) if vol_function_type_value == VolFuncTypes.CLARK.value: - vol = vol_function_clark(params, f, k, t) + gapK + vol = vol_function_clark(params, f, k, t) + gap_k return vol - elif vol_function_type_value == VolFuncTypes.SABR.value: - vol = vol_function_sabr(params, f, k, t) + gapK + + if vol_function_type_value == VolFuncTypes.SABR.value: + vol = vol_function_sabr(params, f, k, t) + gap_k return vol - elif vol_function_type_value == VolFuncTypes.SABR_BETA_HALF.value: - vol = vol_function_sabr_beta_half(params, f, k, t) + gapK + + if vol_function_type_value == VolFuncTypes.SABR_BETA_HALF.value: + vol = vol_function_sabr_beta_half(params, f, k, t) + gap_k return vol - elif vol_function_type_value == VolFuncTypes.SABR_BETA_ONE.value: - vol = vol_function_sabr_beta_one(params, f, k, t) + gapK + + if vol_function_type_value == VolFuncTypes.SABR_BETA_ONE.value: + vol = vol_function_sabr_beta_one(params, f, k, t) + gap_k return vol - elif vol_function_type_value == VolFuncTypes.BBG.value: - vol = vol_function_bloomberg(params, f, k, t) + gapK + + if vol_function_type_value == VolFuncTypes.BBG.value: + vol = vol_function_bloomberg(params, f, k, t) + gap_k return vol - elif vol_function_type_value == VolFuncTypes.CLARK5.value: - vol = vol_function_clark(params, f, k, t) + gapK + + if vol_function_type_value == VolFuncTypes.CLARK5.value: + vol = vol_function_clark(params, f, k, t) + gap_k return vol - else: - raise FinError("Unknown Model Type") + + raise FinError("Unknown Model Type") ############################################################################### @@ -994,7 +999,7 @@ def _delta_fit(k, *args): r_d = args[3] r_f = args[4] option_type_value = args[5] - deltaTypeValue = args[6] + delta_type_value = args[6] inverse_delta_target = args[7] params = args[8] strikes = args[9] @@ -1004,7 +1009,7 @@ def _delta_fit(k, *args): v = vol_function(vol_type_value, params, strikes, gaps, f, k, t) delta_out = fast_delta( - s, t, k, r_d, r_f, v, deltaTypeValue, option_type_value + s, t, k, r_d, r_f, v, delta_type_value, option_type_value ) inverse_delta_out = norminvcdf(np.abs(delta_out)) @@ -1044,7 +1049,7 @@ def _solver_for_smile_strike( rd, rf, option_type_value, - volatilityTypeValue, + vol_type_value, delta_target, delta_method_value, initial_guess, @@ -1059,7 +1064,7 @@ def _solver_for_smile_strike( inverse_delta_target = norminvcdf(np.abs(delta_target)) argtuple = ( - volatilityTypeValue, + vol_type_value, s, t, rd, @@ -1125,14 +1130,14 @@ def solve_for_strike( else: phi = -1.0 - F0T = spot_fx_rate * for_df / dom_df + fwd_fx_rate = spot_fx_rate * for_df / dom_df vsqrtt = volatility * np.sqrt(t_del) arg = delta_target * phi / for_df # CHECK THIS !!! norminvdelta = norminvcdf(arg) - K = F0T * np.exp(-vsqrtt * (phi * norminvdelta - vsqrtt / 2.0)) + K = fwd_fx_rate * np.exp(-vsqrtt * (phi * norminvdelta - vsqrtt / 2.0)) return K - elif delta_method_value == FinFXDeltaMethod.FORWARD_DELTA.value: + if delta_method_value == FinFXDeltaMethod.FORWARD_DELTA.value: dom_df = np.exp(-rd * t_del) for_df = np.exp(-rf * t_del) @@ -1142,14 +1147,14 @@ def solve_for_strike( else: phi = -1.0 - F0T = spot_fx_rate * for_df / dom_df + fwd_fx_rate = spot_fx_rate * for_df / dom_df vsqrtt = volatility * np.sqrt(t_del) arg = delta_target * phi norminvdelta = norminvcdf(arg) - K = F0T * np.exp(-vsqrtt * (phi * norminvdelta - vsqrtt / 2.0)) + K = fwd_fx_rate * np.exp(-vsqrtt * (phi * norminvdelta - vsqrtt / 2.0)) return K - elif delta_method_value == FinFXDeltaMethod.SPOT_DELTA_PREM_ADJ.value: + if delta_method_value == FinFXDeltaMethod.SPOT_DELTA_PREM_ADJ.value: argtuple = ( spot_fx_rate, @@ -1168,7 +1173,7 @@ def solve_for_strike( return K - elif delta_method_value == FinFXDeltaMethod.FORWARD_DELTA_PREM_ADJ.value: + if delta_method_value == FinFXDeltaMethod.FORWARD_DELTA_PREM_ADJ.value: argtuple = ( spot_fx_rate, @@ -1187,9 +1192,7 @@ def solve_for_strike( return K - else: - - raise FinError("Unknown FinFXDeltaMethod") + raise FinError("Unknown FinFXDeltaMethod") ############################################################################### @@ -1506,7 +1509,7 @@ def delta_to_strike(self, call_delta, expiry_dt, delta_method): initial_guess = self.k_atm[index0] - K0 = _solver_for_smile_strike( + k0 = _solver_for_smile_strike( s, t_exp, self.rd[index0], @@ -1523,7 +1526,7 @@ def delta_to_strike(self, call_delta, expiry_dt, delta_method): if index1 != index0: - K1 = _solver_for_smile_strike( + k1 = _solver_for_smile_strike( s, t_exp, self.rd[index1], @@ -1539,18 +1542,18 @@ def delta_to_strike(self, call_delta, expiry_dt, delta_method): ) else: - K1 = K0 + k1 = k0 # In the expiry time dimension, both volatilities are interpolated # at the same strikes but different deltas. if np.abs(t1 - t0) > 1e-6: - K = ((t_exp - t0) * K1 + (t1 - t_exp) * K0) / (t1 - t0) + K = ((t_exp - t0) * k1 + (t1 - t_exp) * k0) / (t1 - t0) else: - K = K1 + K = k1 return K @@ -1618,7 +1621,7 @@ def vol_from_delta_date(self, call_delta, expiry_dt, delta_method=None): initial_guess = self.k_atm[index0] - K0 = _solver_for_smile_strike( + k0 = _solver_for_smile_strike( s, t_exp, self.rd[index0], @@ -1639,13 +1642,13 @@ def vol_from_delta_date(self, call_delta, expiry_dt, delta_method=None): self.strikes[index0], self.gaps[index0], fwd0, - K0, + k0, t0, ) if index1 != index0: - K1 = _solver_for_smile_strike( + k1 = _solver_for_smile_strike( s, t_exp, self.rd[index1], @@ -1666,7 +1669,7 @@ def vol_from_delta_date(self, call_delta, expiry_dt, delta_method=None): self.strikes[index1], self.gaps[index1], fwd1, - K1, + k1, t1, ) else: @@ -1680,7 +1683,7 @@ def vol_from_delta_date(self, call_delta, expiry_dt, delta_method=None): if np.abs(t1 - t0) > 1e-6: vart = ((t_exp - t0) * vart1 + (t1 - t_exp) * vart0) / (t1 - t0) - kt = ((t_exp - t0) * K1 + (t1 - t_exp) * K0) / (t1 - t0) + kt = ((t_exp - t0) * k1 + (t1 - t_exp) * k0) / (t1 - t0) if vart < 0.0: raise FinError( @@ -1692,7 +1695,7 @@ def vol_from_delta_date(self, call_delta, expiry_dt, delta_method=None): else: volt = vol0 - kt = K0 + kt = k0 return volt, kt @@ -1734,7 +1737,7 @@ def _build_vol_surface( self.rd = np.zeros(num_vol_curves) self.rf = np.zeros(num_vol_curves) self.k_atm = np.zeros(num_vol_curves) - self.deltaATM = np.zeros(num_vol_curves) + self.delta_atm = np.zeros(num_vol_curves) self.k_25d_c = np.zeros(num_vol_curves) self.k_25d_p = np.zeros(num_vol_curves) @@ -2044,7 +2047,7 @@ def check_calibration(self, verbose: bool, tol: float = 1e-6): print("CNT_cPD_RF:%9.6f %%" % (self.rf[i] * 100)) print("FWD_RATE: %9.6f" % (self.fwd[i])) - sigma_ATM_out = vol_function( + sigma_atm_out = vol_function( self.vol_func_type.value, self.parameters[i], self.strikes[i], @@ -2060,16 +2063,16 @@ def check_calibration(self, verbose: bool, tol: float = 1e-6): print("VOL_pARAMETERS:", self.parameters[i]) print("======================================================") print("OUT_k_atm: %9.6f" % (self.k_atm[i])) - print("OUT_ATM_VOL: %9.6f %%" % (100.0 * sigma_ATM_out)) + print("OUT_ATM_VOL: %9.6f %%" % (100.0 * sigma_atm_out)) - diff = sigma_ATM_out - self.atm_vols[i] + diff = sigma_atm_out - self.atm_vols[i] if np.abs(diff) > tol: print( "FAILED FIT TO ATM VOL IN: %9.6f OUT: %9.6f DIFF: %9.6f" % ( self.atm_vols[i] * 100.0, - sigma_ATM_out * 100.0, + sigma_atm_out * 100.0, diff * 100.0, ) ) @@ -2077,7 +2080,7 @@ def check_calibration(self, verbose: bool, tol: float = 1e-6): call.strike_fx_rate = self.k_atm[i] put.strike_fx_rate = self.k_atm[i] - model = BlackScholes(sigma_ATM_out) + model = BlackScholes(sigma_atm_out) delta_call = call.delta( self.value_dt, @@ -2330,7 +2333,7 @@ def check_calibration(self, verbose: bool, tol: float = 1e-6): % (self.k_25d_p[i], 100.0 * sigma_k_25d_p, delta_put) ) - sigma_RR = sigma_k_25d_c - sigma_k_25d_p + sigma_rr = sigma_k_25d_c - sigma_k_25d_p if verbose: print( @@ -2338,20 +2341,20 @@ def check_calibration(self, verbose: bool, tol: float = 1e-6): ) print( "RR = VOL_k_25_c - VOL_k_25_p => RR_IN: %9.6f %% RR_OUT: %9.6f %%" - % (100.0 * self.rr_25_delta_vols[i], 100.0 * sigma_RR) + % (100.0 * self.rr_25_delta_vols[i], 100.0 * sigma_rr) ) print( "==========================================================" ) - diff = sigma_RR - self.rr_25_delta_vols[i] + diff = sigma_rr - self.rr_25_delta_vols[i] if np.abs(diff) > tol: print( "FAILED FIT TO 25d RRV IN: % 9.6f OUT: % 9.6f DIFF: % 9.6f" % ( self.rr_25_delta_vols[i] * 100.0, - sigma_RR * 100.0, + sigma_rr * 100.0, diff * 100.0, ) ) @@ -2587,7 +2590,7 @@ def check_calibration(self, verbose: bool, tol: float = 1e-6): % (self.k_10d_p[i], 100.0 * sigma_k_10d_p, delta_put) ) - sigma_RR = sigma_k_10d_c - sigma_k_10d_p + sigma_rr = sigma_k_10d_c - sigma_k_10d_p if verbose: print( @@ -2595,20 +2598,20 @@ def check_calibration(self, verbose: bool, tol: float = 1e-6): ) print( "RR = VOL_k_10d_c - VOL_k_10d_p => RR_IN: %9.6f %% RR_OUT: %9.6f %%" - % (100.0 * self.rr_10_delta_vols[i], 100.0 * sigma_RR) + % (100.0 * self.rr_10_delta_vols[i], 100.0 * sigma_rr) ) print( "=====================================================" ) - diff = sigma_RR - self.rr_10_delta_vols[i] + diff = sigma_rr - self.rr_10_delta_vols[i] if np.abs(diff) > tol: print( "FAILED FIT TO 10d RRV IN: % 9.6f OUT: % 9.6f DIFF: % 9.6f" % ( self.rr_10_delta_vols[i] * 100.0, - sigma_RR * 100.0, + sigma_rr * 100.0, diff * 100.0, ) ) @@ -2621,12 +2624,12 @@ def implied_dbns(self, low_fx, high_fx, num_intervals): dbns = [] - for iTenor in range(0, len(self.tenors)): + for i_tenor in range(0, len(self.tenors)): - f = self.fwd[iTenor] - t = self.t_exp[iTenor] + f = self.fwd[i_tenor] + t = self.t_exp[i_tenor] - dFX = (high_fx - low_fx) / num_intervals + d_fx = (high_fx - low_fx) / num_intervals dom_df = self.domestic_curve._df(t) for_df = self.foreign_curve._df(t) @@ -2634,34 +2637,34 @@ def implied_dbns(self, low_fx, high_fx, num_intervals): r_d = -np.log(dom_df) / t r_f = -np.log(for_df) / t - Ks = [] + ks = [] vols = [] - for iK in range(0, num_intervals): + for ik in range(0, num_intervals): - k = low_fx + iK * dFX + k = low_fx + ik * d_fx vol = vol_function( self.vol_func_type.value, - self.parameters[iTenor], - self.strikes[iTenor], - self.gaps[iTenor], + self.parameters[i_tenor], + self.strikes[i_tenor], + self.gaps[i_tenor], f, k, t, ) - Ks.append(k) + ks.append(k) vols.append(vol) - Ks = np.array(Ks) + ks = np.array(ks) vols = np.array(vols) density = option_implied_dbn( - self.spot_fx_rate, t, r_d, r_f, Ks, vols + self.spot_fx_rate, t, r_d, r_f, ks, vols ) - dbn = FinDistribution(Ks, density) + dbn = FinDistribution(ks, density) dbns.append(dbn) return dbns @@ -2680,41 +2683,41 @@ def plot_vol_curves(self): atm_vol = self.atm_vols[tenor_index] * 100 ms_vol25 = self.ms_25_delta_vols[tenor_index] * 100 - rrVol25 = self.rr_25_delta_vols[tenor_index] * 100 + rr_vol_25 = self.rr_25_delta_vols[tenor_index] * 100 ms_vol10 = self.ms_10_delta_vols[tenor_index] * 100 - rrVol10 = self.rr_10_delta_vols[tenor_index] * 100 + rr_vol_10 = self.rr_10_delta_vols[tenor_index] * 100 strikes = self.strikes[tenor_index] gaps = self.gaps[tenor_index] - low_K = self.k_10d_p[tenor_index] * 0.90 + low_k = self.k_10d_p[tenor_index] * 0.90 high_k = self.k_10d_c_ms[tenor_index] * 1.10 ks = [] vols = [] num_intervals = 30 - K = low_K - dK = (high_k - low_K) / num_intervals + k = low_k + dk = (high_k - low_k) / num_intervals params = self.parameters[tenor_index] t = self.t_exp[tenor_index] f = self.fwd[tenor_index] - for i in range(0, num_intervals): + for _ in range(0, num_intervals): sigma = ( - vol_function(vol_type_val, params, strikes, gaps, f, K, t) + vol_function(vol_type_val, params, strikes, gaps, f, k, t) * 100.0 ) - ks.append(K) + ks.append(k) vols.append(sigma) - K = K + dK + k = k + dk label_str = self.tenors[tenor_index] label_str += " ATM: " + str(atm_vol)[0:6] label_str += " MS25: " + str(ms_vol25)[0:6] - label_str += " RR25: " + str(rrVol25)[0:6] + label_str += " RR25: " + str(rr_vol_25)[0:6] label_str += " MS10: " + str(ms_vol10)[0:6] - label_str += " RR10: " + str(rrVol10)[0:6] + label_str += " RR10: " + str(rr_vol_10)[0:6] plt.plot(ks, vols, label=label_str) plt.xlabel("Strike") @@ -2779,7 +2782,8 @@ def plot_vol_curves(self): ########################################################################### def __repr__(self): - s = label_to_string("OBJECT TYPE", type(self)._name__) + + s = label_to_string("OBJECT TYPE", type(self).__name__) s += label_to_string("VALUE DATE", self.value_dt) s += label_to_string("FX RATE", self.spot_fx_rate) s += label_to_string("CCY PAIR", self.currency_pair) @@ -2804,7 +2808,7 @@ def __repr__(self): s += label_to_string("RR VOLS", self.rr_25_delta_vols[i] * 100.0) s += label_to_string("ATM Strike", self.k_atm[i]) - s += label_to_string("ATM Delta", self.deltaATM[i]) + s += label_to_string("ATM Delta", self.delta_atm[i]) s += label_to_string("k_atm", self.k_atm[i]) diff --git a/financepy/market/volatility/ibor_cap_vol_curve_fn.py b/financepy/market/volatility/ibor_cap_vol_curve_fn.py index 6d25d0d4..a4dbc6bd 100644 --- a/financepy/market/volatility/ibor_cap_vol_curve_fn.py +++ b/financepy/market/volatility/ibor_cap_vol_curve_fn.py @@ -8,21 +8,16 @@ from ...utils.date import Date from ...utils.global_vars import g_days_in_year -########################################################################## +############################################################################### # TODO: Market calibration (fitting) -########################################################################## +############################################################################### -class IborCapVolCurveFn(): - """ Class to manage a term structure of caplet volatilities using the - parametric form suggested by Rebonato (1999). """ +class IborCapVolCurveFn: + """Class to manage a term structure of caplet volatilities using the + parametric form suggested by Rebonato (1999).""" - def __init__(self, - curve_dt, - a, - b, - c, - d): + def __init__(self, curve_dt, a, b, c, d): self._curve_dt = curve_dt self._a = a @@ -30,18 +25,22 @@ def __init__(self, self._c = c self._d = d -############################################################################### + ########################################################################### def cap_floorlet_vol(self, dt): - """ Return the caplet volatility. """ + """Return the caplet volatility.""" if isinstance(dt, Date): t = (dt - self._curve_dt) / g_days_in_year - vol = (self._a + self._b*t) * np.exp(-self._c*t) + self._d + else: + t = dt + + vol = (self._a + self._b * t) * np.exp(-self._c * t) + self._d if vol < 0.0: raise FinError("Negative volatility. Not permitted.") return vol + ###############################################################################