diff --git a/financepy/__init__.py b/financepy/__init__.py index c1a5b74a..5edfcd38 100644 --- a/financepy/__init__.py +++ b/financepy/__init__.py @@ -1,7 +1,7 @@ cr = "\n" s = "####################################################################" + cr -s += "# FINANCEPY BETA Version " + str('0.300') + " - This build: 29 May 2023 at 11:50 #" + cr +s += "# FINANCEPY BETA Version " + str('0.300') + " - This build: 22 Aug 2023 at 17:29 #" + cr s += "# This software is distributed FREE AND WITHOUT ANY WARRANTY #" + cr s += "# Report bugs as issues at https://github.com/domokane/FinancePy #" + cr s += "####################################################################" diff --git a/financepy/products/bonds/bond.py b/financepy/products/bonds/bond.py index 73391f94..80e9e991 100644 --- a/financepy/products/bonds/bond.py +++ b/financepy/products/bonds/bond.py @@ -62,17 +62,16 @@ class YTMCalcType(Enum): ############################################################################### -def _f(y, *args): +def _f(ytm, *args): """ Function used to do root search in price to yield calculation. """ bond = args[0] settlement_date = args[1] price = args[2] convention = args[3] - px = bond.full_price_from_ytm(settlement_date, y, convention) + px = bond.dirty_price_from_ytm(settlement_date, ytm, convention) obj_fn = px - price return obj_fn - ############################################################################### @@ -82,7 +81,7 @@ def _g(oas, *args): settlement_date = args[1] price = args[2] discount_curve = args[3] - px = bond.full_price_from_oas(settlement_date, discount_curve, oas) + px = bond.dirty_price_from_oas(settlement_date, discount_curve, oas) obj_fn = px - price return obj_fn @@ -99,7 +98,7 @@ def __init__(self, coupon: float, # Annualised bond coupon freq_type: FrequencyTypes, accrual_type: DayCountTypes, - face_amount: float = 100.0, + ex_div_days: int = 0, calendar_type: CalendarTypes = CalendarTypes.WEEKEND, bus_day_rule_type = BusDayAdjustTypes.FOLLOWING, date_gen_rule_type = DateGenRuleTypes.BACKWARD): @@ -131,9 +130,8 @@ def __init__(self, self._accrual_type = accrual_type self._frequency = annual_frequency(freq_type) - self._face_amount = face_amount # This is the bond holding size + self._ex_div_days = ex_div_days self._par = 100.0 # This is how price is quoted and amount at maturity - self._redemption = 1.0 # This is amount paid at maturity self._calendar_type = calendar_type self._coupon_dates = [] @@ -214,18 +212,18 @@ def _calculate_flows(self): ########################################################################### - def full_price_from_ytm(self, + def dirty_price_from_ytm(self, settlement_date: Date, ytm: float, convention: YTMCalcType = YTMCalcType.UK_DMO): - """ Calculate the full price of bond from its yield to maturity. This + """ Calculate the dirty price of bond from its yield to maturity. This function is vectorised with respect to the yield input. It implements a number of standard conventions for calculating the YTM. """ if convention not in YTMCalcType: raise FinError("Yield convention unknown." + str(convention)) - self.calc_accrued_interest(settlement_date) + self.accrued_interest(settlement_date) ytm = np.array(ytm) # VECTORIZED ytm = ytm + 0.000000000012345 # SNEAKY LOW-COST TRICK TO AVOID y=0 @@ -250,33 +248,33 @@ def full_price_from_ytm(self, if convention == YTMCalcType.UK_DMO: if n == 0: - fp = (v ** (self._alpha)) * (self._redemption + c / f) + dp = (v ** (self._alpha)) * (1.0 + c / f) else: term1 = (c / f) term2 = (c / f) * v term3 = (c / f) * v * v * (1.0 - v ** (n - 1)) / (1.0 - v) - term4 = self._redemption * (v ** n) - fp = (v ** (self._alpha)) * (term1 + term2 + term3 + term4) + term4 = (v ** n) + dp = (v ** (self._alpha)) * (term1 + term2 + term3 + term4) elif convention == YTMCalcType.US_TREASURY: if n == 0: - fp = (v ** (self._alpha)) * (self._redemption + c / f) + dp = (v ** (self._alpha)) * (1.0 + c / f) else: term1 = (c / f) term2 = (c / f) * v term3 = (c / f) * v * v * (1.0 - v ** (n - 1)) / (1.0 - v) - term4 = self._redemption * (v ** n) + term4 = (v ** n) vw = 1.0 / (1.0 + self._alpha * ytm / f) - fp = (vw) * (term1 + term2 + term3 + term4) + dp = (vw) * (term1 + term2 + term3 + term4) elif convention == YTMCalcType.US_STREET: if n == 0: vw = 1.0 / (1.0 + self._alpha * ytm / f) - fp = vw * (self._redemption + c / f) + dp = vw * (1.0 + c / f) else: term1 = (c / f) term2 = (c / f) * v term3 = (c / f) * v * v * (1.0 - v ** (n - 1)) / (1.0 - v) - term4 = self._redemption * (v ** n) - fp = (v ** (self._alpha)) * (term1 + term2 + term3 + term4) + term4 = (v ** n) + dp = (v ** (self._alpha)) * (term1 + term2 + term3 + term4) elif convention == YTMCalcType.CFETS: if n == 0: last_year = self._maturity_date.add_tenor("-12M") @@ -285,31 +283,32 @@ def full_price_from_ytm(self, self._maturity_date, freq_type=FrequencyTypes.ANNUAL)[0]) vw = 1.0 / (1.0 + alpha * ytm) - fp = vw * (self._redemption + c / f) + dp = vw * (1.0 + c / f) else: term1 = (c / f) term2 = (c / f) * v term3 = (c / f) * v * v * (1.0 - v ** (n - 1)) / (1.0 - v) - term4 = self._redemption * (v ** n) - fp = (v ** (self._alpha)) * (term1 + term2 + term3 + term4) + term4 = (v ** n) + dp = (v ** (self._alpha)) * (term1 + term2 + term3 + term4) else: raise FinError("Unknown yield convention") - return fp * self._par + return dp * self._par ########################################################################### def principal(self, settlement_date: Date, - y: float, + ytm: float, + face: (float), convention: YTMCalcType): """ Calculate the principal value of the bond based on the face amount from its discount margin and making assumptions about the future Ibor rates. """ - full_price = self.full_price_from_ytm(settlement_date, y, convention) + dirty_price = self.dirty_price_from_ytm(settlement_date, ytm, convention) - principal = full_price * self._face_amount / self._par + principal = dirty_price * face / self._par principal = principal - self._accrued_interest return principal @@ -323,8 +322,8 @@ def dollar_duration(self, known as the DV01 in Bloomberg. """ dy = 0.0001 # 1 basis point - p0 = self.full_price_from_ytm(settlement_date, ytm - dy, convention) - p2 = self.full_price_from_ytm(settlement_date, ytm + dy, convention) + p0 = self.dirty_price_from_ytm(settlement_date, ytm - dy, convention) + p2 = self.dirty_price_from_ytm(settlement_date, ytm + dy, convention) durn = -(p2 - p0) / dy / 2.0 return durn @@ -338,7 +337,7 @@ def macauley_duration(self, given its yield to maturity. """ dd = self.dollar_duration(settlement_date, ytm, convention) - fp = self.full_price_from_ytm(settlement_date, ytm, convention) + fp = self.dirty_price_from_ytm(settlement_date, ytm, convention) md = dd * (1.0 + ytm / self._frequency) / fp return md @@ -352,7 +351,7 @@ def modified_duration(self, given its yield to maturity. """ dd = self.dollar_duration(settlement_date, ytm, convention) - fp = self.full_price_from_ytm(settlement_date, ytm, convention) + fp = self.dirty_price_from_ytm(settlement_date, ytm, convention) md = dd / fp return md @@ -429,9 +428,8 @@ def key_rate_durations(bond, par_crv = BondZeroCurve(settlement_date, par_bonds, clean_prices, InterpTypes.LINEAR_ZERO_RATES) - # calculate the full price of the bond using the discount curve - p_zero = bond.full_price_from_discount_curve(settlement_date, par_crv) - + # calculate the dirty price of the bond using the discount curve + p_zero = bond.dirty_price_from_discount_curve(settlement_date, par_crv) # shift up by the yield of corresponding par bond rates[ind] += shift @@ -454,7 +452,7 @@ def key_rate_durations(bond, # calculate the full price of the bond # using the discount curve with the key rate shifted up - p_up = bond.full_price_from_discount_curve(settlement_date, par_crv_up) + p_up = bond.dirty_price_from_discount_curve(settlement_date, par_crv_up) # create a curve again with the key rate shifted down # by twice the shift value. @@ -480,7 +478,7 @@ def key_rate_durations(bond, InterpTypes.LINEAR_ZERO_RATES) # calculate the full price of the bond using - p_down = bond.full_price_from_discount_curve(settlement_date, par_crv_down) + p_down = bond.dirty_price_from_discount_curve(settlement_date, par_crv_down) # calculate the key rate duration # using the formula (P_down - P_up) / (2 * shift * P_zero) @@ -501,9 +499,9 @@ def convexity_from_ytm(self, function is vectorised with respect to the yield input. """ dy = 0.0001 - p0 = self.full_price_from_ytm(settlement_date, ytm - dy, convention) - p1 = self.full_price_from_ytm(settlement_date, ytm, convention) - p2 = self.full_price_from_ytm(settlement_date, ytm + dy, convention) + p0 = self.dirty_price_from_ytm(settlement_date, ytm - dy, convention) + p1 = self.dirty_price_from_ytm(settlement_date, ytm, convention) + p2 = self.dirty_price_from_ytm(settlement_date, ytm + dy, convention) conv = ((p2 + p0) - 2.0 * p1) / dy / dy / p1 / self._par return conv @@ -516,9 +514,9 @@ def clean_price_from_ytm(self, """ Calculate the bond clean price from the yield to maturity. This function is vectorised with respect to the yield input. """ - full_price = self.full_price_from_ytm(settlement_date, ytm, convention) - accrued_amount = self._accrued_interest * self._par / self._face_amount - clean_price = full_price - accrued_amount + dirty_price = self.dirty_price_from_ytm(settlement_date, ytm, convention) + accrued = self.accrued_interest(settlement_date, self._par) + clean_price = dirty_price - accrued return clean_price ########################################################################### @@ -530,17 +528,17 @@ def clean_price_from_discount_curve(self, present-value the bond's cash flows back to the curve anchor date and not to the settlement date. """ - self.calc_accrued_interest(settlement_date) - full_price = self.full_price_from_discount_curve(settlement_date, + self.accrued_interest(settlement_date) + dirty_price = self.dirty_price_from_discount_curve(settlement_date, discount_curve) - accrued = self._accrued_interest * self._par / self._face_amount - clean_price = full_price - accrued + accrued = self.accrued_interest(settlement_date, self._par) + clean_price = dirty_price - accrued return clean_price ########################################################################### - def full_price_from_discount_curve(self, + def dirty_price_from_discount_curve(self, settlement_date: Date, discount_curve: DiscountCurve): """ Calculate the bond price using a provided discount curve to PV the @@ -567,7 +565,7 @@ def full_price_from_discount_curve(self, pv = flow * df px += pv - px += df * self._redemption + px += df px = px / dfSettle return px * self._par @@ -598,13 +596,15 @@ def yield_to_maturity(self, raise FinError("Unknown type for clean_price " + str(type(clean_price))) - self.calc_accrued_interest(settlement_date) - accrued_amount = self._accrued_interest * self._par / self._face_amount - full_prices = (clean_prices + accrued_amount) + self.accrued_interest(settlement_date, 1.0) + + accrued_amount = self._accrued_interest * self._par + dirty_prices = (clean_prices + accrued_amount) ytms = [] + + for dirty_price in dirty_prices: - for full_price in full_prices: - argtuple = (self, settlement_date, full_price, convention) + argtuple = (self, settlement_date, dirty_price, convention) ytm = optimize.newton(_f, x0=0.05, # guess initial value of 5% @@ -623,18 +623,18 @@ def yield_to_maturity(self, ########################################################################### - def calc_accrued_interest(self, - settlement_date: Date, - num_ex_dividend_days: int = 0, - calendar_type: CalendarTypes = CalendarTypes.WEEKEND): + def accrued_interest(self, + settlement_date: Date, + face:float = 100.0): """ Calculate the amount of coupon that has accrued between the previous coupon date and the settlement date. Note that for some day count schemes (such as 30E/360) this is not actually the number of days between the previous coupon payment date and settlement date. If the bond trades with ex-coupon dates then you need to supply the number of days before the coupon date the ex-coupon date is. You can specify the - calendar to be used - NONE means only calendar days, WEEKEND is only - weekends or you can specify a country calendar for business days.""" + calendar to be used in the bond constructor - NONE means only calendar + days, WEEKEND is only weekends or you can specify a country calendar + for business days.""" num_flows = len(self._coupon_dates) @@ -649,21 +649,23 @@ def calc_accrued_interest(self, break dc = DayCount(self._accrual_type) - cal = Calendar(calendar_type) - exDividend_date = cal.add_business_days( - self._ncd, -num_ex_dividend_days) + cal = Calendar(self._calendar_type) + + ex_div_date = cal.add_business_days( + self._ncd, -self._ex_div_days) (acc_factor, num, _) = dc.year_frac(self._pcd, settlement_date, self._ncd, self._freq_type) - if settlement_date > exDividend_date: + if settlement_date > ex_div_date: acc_factor = acc_factor - 1.0 / self._frequency self._alpha = 1.0 - acc_factor * self._frequency - self._accrued_interest = acc_factor * self._face_amount * self._coupon + self._accrued_interest = acc_factor * self._coupon * face self._accrued_days = num + return self._accrued_interest ########################################################################### @@ -683,8 +685,8 @@ def asset_swap_spread( respect to the clean price. """ clean_price = np.array(clean_price) - self.calc_accrued_interest(settlement_date) - accrued_amount = self._accrued_interest * self._par / self._face_amount + self.accrued_interest(settlement_date) + accrued_amount = self._accrued_interest * self._par bondPrice = clean_price + accrued_amount # Calculate the price of the bond discounted on the Ibor curve pvIbor = 0.0 @@ -697,7 +699,7 @@ def asset_swap_spread( df = discount_curve.df(dt) pvIbor += df * self._coupon / self._frequency - pvIbor += df * self._redemption + pvIbor += df # Calculate the PV01 of the floating leg of the asset swap # I assume here that the coupon starts accruing on the settlement date @@ -724,14 +726,14 @@ def asset_swap_spread( ########################################################################### - def full_price_from_oas(self, + def dirty_price_from_oas(self, settlement_date: Date, discount_curve: DiscountCurve, oas: float): """ Calculate the full price of the bond from its OAS given the bond settlement date, a discount curve and the oas as a number. """ - self.calc_accrued_interest(settlement_date) + self.accrued_interest(settlement_date) f = self._frequency c = self._coupon @@ -753,11 +755,11 @@ def full_price_from_oas(self, df_adjusted = np.power(1.0 + (r + oas) / f, -t * f) pv = pv + (c / f) * df_adjusted - pv = pv + df_adjusted * self._redemption + pv = pv + df_adjusted pv *= self._par return pv - ########################################################################### + ############################################################################ def option_adjusted_spread(self, settlement_date: Date, @@ -774,15 +776,15 @@ def option_adjusted_spread(self, raise FinError("Unknown type for clean_price " + str(type(clean_price))) - self.calc_accrued_interest(settlement_date) + self.accrued_interest(settlement_date) - accrued_amount = self._accrued_interest * self._par / self._face_amount - full_prices = clean_prices + accrued_amount + accrued_amount = self._accrued_interest * self._par + dirty_prices = clean_prices + accrued_amount oass = [] - for full_price in full_prices: - argtuple = (self, settlement_date, full_price, discount_curve) + for dirty_price in dirty_prices: + argtuple = (self, settlement_date, dirty_price, discount_curve) oas = optimize.newton(_g, x0=0.01, # initial value of 1% @@ -832,7 +834,7 @@ def print_coupon_dates(self, ########################################################################### - def full_price_from_survival_curve(self, + def dirty_price_from_survival_curve(self, settlement_date: Date, discount_curve: DiscountCurve, survival_curve: DiscountCurve, @@ -895,12 +897,12 @@ def clean_price_from_survival_curve(self, self.calc_accrued_interest(settlement_date) - full_price = self.full_price_from_survival_curve(settlement_date, + dirty_price = self.dirty_price_from_survival_curve(settlement_date, discount_curve, survival_curve, recovery_rate) - clean_price = full_price - self._accrued_interest + clean_price = dirty_price - self._accrued_interest return clean_price ########################################################################### @@ -916,8 +918,8 @@ def calc_ror(self, This function computes the full prices at buying and selling, plus the coupon payments during the period. It returns a tuple which includes a simple rate of return, a compounded IRR and the PnL. """ - buy_price = self.full_price_from_ytm(begin_date, begin_ytm, convention) - sell_price = self.full_price_from_ytm(end_date, end_ytm, convention) + buy_price = self.dirty_price_from_ytm(begin_date, begin_ytm, convention) + sell_price = self.dirty_price_from_ytm(end_date, end_ytm, convention) dates_cfs = zip(self._coupon_dates, self._flow_amounts) # The coupon or par payments on buying date belong to the buyer. # The coupon or par payments on selling date are given to the new buyer. @@ -952,7 +954,7 @@ def __repr__(self): s += label_to_string("COUPON (%)", self._coupon*100.0) s += label_to_string("FREQUENCY", self._freq_type) s += label_to_string("ACCRUAL TYPE", self._accrual_type) - s += label_to_string("FACE AMOUNT", self._face_amount, "") + s += label_to_string("EX_DIV DAYS", self._ex_div_days, "") return s ########################################################################### diff --git a/financepy/products/bonds/bond_callable.py b/financepy/products/bonds/bond_callable.py index 0cf69093..d0b7eb58 100644 --- a/financepy/products/bonds/bond_callable.py +++ b/financepy/products/bonds/bond_callable.py @@ -68,12 +68,14 @@ def __init__(self, self._freq_type = freq_type self._accrual_type = accrual_type + ex_div_days= 0 + self._bond = Bond(issue_date, maturity_date, coupon, freq_type, accrual_type, - face_amount) + ex_div_days) # Validate call and put schedules for dt in call_dates: @@ -168,7 +170,7 @@ def value(self, df_times = discount_curve._times df_values = discount_curve._dfs - face_amount = self._bond._face_amount + face_amount = 100.0 if isinstance(model, HWTree): @@ -228,7 +230,7 @@ def __repr__(self): s += label_to_string("COUPON", self._coupon) s += label_to_string("FREQUENCY", self._freq_type) s += label_to_string("ACCRUAL TYPE", self._accrual_type) - s += label_to_string("FACE AMOUNT", self._face_amount) + s += label_to_string("EX-DIV DAYS", self._ex_div_days) s += label_to_string("NUM CALL DATES", len(self._call_dates)) for i in range(0, len(self._call_dates)): diff --git a/financepy/products/bonds/bond_future.py b/financepy/products/bonds/bond_future.py index 5c0ebb8c..e831d77d 100644 --- a/financepy/products/bonds/bond_future.py +++ b/financepy/products/bonds/bond_future.py @@ -50,7 +50,8 @@ def conversion_factor(self, roundedTmatInMonths = int(tmat * 4.0) * 3 newMat = self._first_delivery_date.add_months(roundedTmatInMonths) face = 1.0 - + ex_div_days = 0 + issue_date = Date(newMat._d, newMat._m, 2000) newBond = Bond(issue_date, @@ -58,7 +59,7 @@ def conversion_factor(self, bond._coupon, bond._freq_type, bond._accrual_type, - face) + ex_div_days) p = newBond.clean_price_from_ytm(self._first_delivery_date, self._coupon) diff --git a/financepy/products/bonds/bond_market.py b/financepy/products/bonds/bond_market.py index a8ccbbb6..74599adc 100644 --- a/financepy/products/bonds/bond_market.py +++ b/financepy/products/bonds/bond_market.py @@ -64,7 +64,8 @@ def get_bond_market_conventions(country): semi_annual = FrequencyTypes.SEMI_ANNUAL act_act = DayCountTypes.ACT_ACT_ICMA thirtye360 = DayCountTypes.THIRTY_E_360 - + thirty360 = DayCountTypes.THIRTY_360_BOND + # TODO: CHECK CONVENTIONS # RETURNS @@ -105,6 +106,10 @@ def get_bond_market_conventions(country): return (act_act, semi_annual, 2, 0, None) elif country == BondMarkets.NETHERLANDS: return (act_act, annual, 2, 0, None) + elif country == BondMarkets.NEW_ZEALAND: + return (act_act, annual, 2, 0, None) + elif country == BondMarkets.NORWAY: + return (thirty360, annual, 2, 2, None) elif country == BondMarkets.PORTUGAL: return (act_act, annual, 2, 0, None) elif country == BondMarkets.SLOVAKIA: diff --git a/financepy/products/bonds/bond_zero.py b/financepy/products/bonds/bond_zero.py index 3b569f33..64c16b35 100644 --- a/financepy/products/bonds/bond_zero.py +++ b/financepy/products/bonds/bond_zero.py @@ -26,7 +26,7 @@ def _f(y, *args): settlement_date = args[1] price = args[2] convention = args[3] - px = bond.full_price_from_ytm(settlement_date, y, convention) + px = bond.dirty_price_from_ytm(settlement_date, y, convention) obj_fn = px - price return obj_fn @@ -40,7 +40,7 @@ def _g(oas, *args): settlement_date = args[1] price = args[2] discount_curve = args[3] - px = bond.full_price_from_oas(settlement_date, discount_curve, oas) + px = bond.dirty_price_from_oas(settlement_date, discount_curve, oas) obj_fn = px - price return obj_fn @@ -75,22 +75,22 @@ def __init__(self, self._issue_date = issue_date self._maturity_date = maturity_date self._accrual_type = DayCountTypes.ZERO - self._face_amount = face_amount # This is the bond holding size self._issue_price = issue_price # Price of issue, usually discounted self._par = 100.0 # This is how price is quoted and amount at maturity - self._redemption = 1.0 # This is amount paid at maturity self._freq_type = FrequencyTypes.ZERO self._coupon_dates = [issue_date, maturity_date] self._payment_dates = [issue_date, maturity_date] self._flow_amounts = [0.0, 0.0] # coupon payments are zero - + self._calendar_type = CalendarTypes.WEEKEND + self._ex_div_days = 0 + self._accrued_interest = None self._accrued_days = 0.0 self._alpha = 0.0 ########################################################################### - def full_price_from_ytm(self, + def dirty_price_from_ytm(self, settlement_date: Date, ytm: float, convention: YTMCalcType = YTMCalcType.ZERO): @@ -101,7 +101,7 @@ def full_price_from_ytm(self, if convention != YTMCalcType.ZERO: raise FinError("Need to use YTMCalcType.ZERO for zero coupon bond") - self.calc_accrued_interest(settlement_date) + self.accrued_interest(settlement_date, 1.0) ytm = np.array(ytm) # VECTORIZED ytm = ytm + 0.000000000012345 # SNEAKY LOW-COST TRICK TO AVOID y=0 @@ -134,15 +134,16 @@ def full_price_from_ytm(self, def principal(self, settlement_date: Date, - y: float, + ytm: float, + face: (float), convention: YTMCalcType = YTMCalcType.ZERO): """ Calculate the principal value of the bond based on the face amount from its discount margin and making assumptions about the future Ibor rates. """ - full_price = self.full_price_from_ytm(settlement_date, y, convention) + full_price = self.dirty_price_from_ytm(settlement_date, y, convention) - principal = full_price * self._face_amount / self._par + principal = dirty_price * face / self._par principal = principal - self._accrued_interest return principal @@ -156,8 +157,8 @@ def dollar_duration(self, known as the DV01 in Bloomberg. """ dy = 0.0001 # 1 basis point - p0 = self.full_price_from_ytm(settlement_date, ytm - dy, convention) - p2 = self.full_price_from_ytm(settlement_date, ytm + dy, convention) + p0 = self.dirty_price_from_ytm(settlement_date, ytm - dy, convention) + p2 = self.dirty_price_from_ytm(settlement_date, ytm + dy, convention) durn = -(p2 - p0) / dy / 2.0 return durn @@ -171,7 +172,7 @@ def macauley_duration(self, given its yield to maturity. """ dd = self.dollar_duration(settlement_date, ytm, convention) - fp = self.full_price_from_ytm(settlement_date, ytm, convention) + fp = self.dirty_price_from_ytm(settlement_date, ytm, convention) md = dd * (1.0 + ytm) / fp return md @@ -185,7 +186,7 @@ def modified_duration(self, given its yield to maturity. """ dd = self.dollar_duration(settlement_date, ytm, convention) - fp = self.full_price_from_ytm(settlement_date, ytm, convention) + fp = self.dirty_price_from_ytm(settlement_date, ytm, convention) md = dd / fp return md @@ -199,9 +200,9 @@ def convexity_from_ytm(self, function is vectorised with respect to the yield input. """ dy = 0.0001 - p0 = self.full_price_from_ytm(settlement_date, ytm - dy, convention) - p1 = self.full_price_from_ytm(settlement_date, ytm, convention) - p2 = self.full_price_from_ytm(settlement_date, ytm + dy, convention) + p0 = self.dirty_price_from_ytm(settlement_date, ytm - dy, convention) + p1 = self.dirty_price_from_ytm(settlement_date, ytm, convention) + p2 = self.dirty_price_from_ytm(settlement_date, ytm + dy, convention) conv = ((p2 + p0) - 2.0 * p1) / dy / dy / p1 / self._par return conv @@ -214,9 +215,9 @@ def clean_price_from_ytm(self, """ Calculate the bond clean price from the yield to maturity. This function is vectorised with respect to the yield input. """ - full_price = self.full_price_from_ytm(settlement_date, ytm, convention) - accrued_amount = self._accrued_interest * self._par / self._face_amount - clean_price = full_price - accrued_amount + full_price = self.dirty_price_from_ytm(settlement_date, ytm, convention) + accrued = self.accrued_interest(settlement_date, self._par) + clean_price = full_price - accrued return clean_price ########################################################################### @@ -229,16 +230,16 @@ def clean_price_from_discount_curve(self, not to the settlement date. """ self.calc_accrued_interest(settlement_date) - full_price = self.full_price_from_discount_curve(settlement_date, + full_price = self.dirty_price_from_discount_curve(settlement_date, discount_curve) - accrued = self._accrued_interest * self._par / self._face_amount + accrued = self.accrued_interest(settlement_date, self._par) clean_price = full_price - accrued return clean_price ########################################################################### - def full_price_from_discount_curve(self, + def dirty_price_from_discount_curve(self, settlement_date: Date, discount_curve: DiscountCurve): """ Calculate the bond price using a provided discount curve to PV the @@ -304,13 +305,17 @@ def yield_to_maturity(self, raise FinError("Unknown type for clean_price " + str(type(clean_price))) - self.calc_accrued_interest(settlement_date) - accrued_amount = self._accrued_interest * self._par / self._face_amount - full_prices = (clean_prices + accrued_amount) + self.accrued_interest(settlement_date, 1.0) + + accrued_amount = self._accrued_interest * self._par + + dirty_prices = (clean_prices + accrued_amount) + ytms = [] - for full_price in full_prices: - argtuple = (self, settlement_date, full_price, convention) + for dirty_price in dirty_prices: + + argtuple = (self, settlement_date, dirty_price, convention) ytm = optimize.newton(_f, x0=0.05, # guess initial value of 5% @@ -329,10 +334,9 @@ def yield_to_maturity(self, ########################################################################### - def calc_accrued_interest(self, - settlement_date: Date, - num_ex_dividend_days: int = 0, - calendar_type: CalendarTypes = CalendarTypes.WEEKEND): + def accrued_interest(self, + settlement_date: Date, + face: (float)): """ Calculate the amount of coupon that has accrued between the previous coupon date and the settlement date. Note that for some day count schemes (such as 30E/360) this is not actually the number of days @@ -355,9 +359,9 @@ def calc_accrued_interest(self, break dc = DayCount(self._accrual_type) - cal = Calendar(calendar_type) + cal = Calendar(self._calendar_type) exDividend_date = cal.add_business_days( - self._ncd, -num_ex_dividend_days) + self._ncd, -self._ex_div_days) (acc_factor, num, _) = dc.year_frac(self._pcd, settlement_date, @@ -370,7 +374,7 @@ def calc_accrued_interest(self, self._alpha = 1.0 - acc_factor interest = (self._par - self._issue_price) * (settlement_date - self._issue_date) / \ (self._maturity_date - self._issue_date) - self._accrued_interest = interest * self._face_amount / self._par + self._accrued_interest = interest self._accrued_days = num return self._accrued_interest @@ -612,8 +616,8 @@ def calc_ror(self, IRR and the PnL. """ - buy_price = self.full_price_from_ytm(begin_date, begin_ytm, convention) - sell_price = self.full_price_from_ytm(end_date, end_ytm, convention) + buy_price = self.dirty_price_from_ytm(begin_date, begin_ytm, convention) + sell_price = self.dirty_price_from_ytm(end_date, end_ytm, convention) dates_cfs = zip(self._coupon_dates, self._flow_amounts) @@ -652,7 +656,6 @@ def __repr__(self): s += label_to_string("Issue Price ", self._issue_price) s += label_to_string("FREQUENCY", self._freq_type) s += label_to_string("ACCRUAL TYPE", self._accrual_type) - s += label_to_string("FACE AMOUNT", self._face_amount, "") return s ########################################################################### diff --git a/financepy/products/credit/cds.py b/financepy/products/credit/cds.py index 1bcbe869..320e9f98 100644 --- a/financepy/products/credit/cds.py +++ b/financepy/products/credit/cds.py @@ -24,6 +24,7 @@ useFlatHazardRateIntegral = True standard_recovery_rate = 0.40 +glob_num_steps_per_year = 25 ############################################################################### # TODO: Perform protection leg pv analytically using fact that hazard rate and @@ -50,13 +51,22 @@ def _risky_pv01_numba(teff, method = InterpTypes.FLAT_FWD_RATES.value + if 1==0: + print("===================") + print("Teff", teff) + print("Acc", accrual_factorPCDToNow) + print("Payments", paymentTimes) + print("Alphas", year_fracs) + print("QTimes", npSurvTimes) + print("QValues", npSurvValues) + couponAccruedIndicator = 1 # Method 0 : This is the market standard which assumes that the coupon # accrued is treated as though on average default occurs roughly midway # through a coupon period. - tncd = paymentTimes[1] + tncd = paymentTimes[0] # The first coupon is a special case which needs to be handled carefully # taking into account what coupon has already accrued and what has not @@ -81,7 +91,7 @@ def _risky_pv01_numba(teff, (qeff - q1) * (year_fracs[1] - accrual_factorPCDToNow) \ * couponAccruedIndicator - for it in range(2, len(paymentTimes)): + for it in range(1, len(paymentTimes)): t2 = paymentTimes[it] @@ -139,7 +149,10 @@ def _protection_leg_pv_numba(teff, the numerical integration over time. """ method = InterpTypes.FLAT_FWD_RATES.value - dt = (tmat - teff) / num_steps_per_year + dt = 1.0 / num_steps_per_year + num_steps = int((tmat-teff) * num_steps_per_year) + dt = (tmat - teff) / num_steps + t = teff z1 = _uinterpolate(t, npIborTimes, npIborValues, method) q1 = _uinterpolate(t, npSurvTimes, npSurvValues, method) @@ -147,9 +160,9 @@ def _protection_leg_pv_numba(teff, prot_pv = 0.0 small = 1e-8 - if useFlatHazardRateIntegral is True: + if useFlatHazardRateIntegral: - for _ in range(0, num_steps_per_year): + for _ in range(0, num_steps): t = t + dt z2 = _uinterpolate(t, npIborTimes, npIborValues, method) q2 = _uinterpolate(t, npSurvTimes, npSurvValues, method) @@ -165,7 +178,7 @@ def _protection_leg_pv_numba(teff, else: - for _ in range(0, num_steps_per_year): + for _ in range(0, num_steps): t += dt z2 = _uinterpolate(t, npIborTimes, npIborValues, method) q2 = _uinterpolate(t, npSurvTimes, npSurvValues, method) @@ -176,6 +189,8 @@ def _protection_leg_pv_numba(teff, z1 = z2 prot_pv = prot_pv * (1.0 - contract_recovery_rate) + +# print("ProtPV", teff, tmat, prot_pv) return prot_pv @@ -236,25 +251,31 @@ def _generate_adjusted_cds_payment_dates(self): frequency = annual_frequency(self._freq_type) calendar = Calendar(self._calendar_type) start_date = self._step_in_date - end_date = self._maturity_date self._payment_dates = [] self._accrual_start_dates = [] self._accrual_end_dates = [] num_months = int(12.0 / frequency) + # We generate unadjusted dates - not adjusted for weekends or holidays unadjusted_schedule_dates = [] if self._date_gen_rule_type == DateGenRuleTypes.BACKWARD: - next_date = end_date + # We start at end date and step backwards + + next_date = self._maturity_date + unadjusted_schedule_dates.append(next_date) + # the unadjusted dates start at end date and end at previous coupon date while next_date > start_date: next_date = next_date.add_months(-num_months) unadjusted_schedule_dates.append(next_date) + # now we adjust for holiday using business day adjustment convention specified adjusted_dates = [] + for date in reversed(unadjusted_schedule_dates): adjusted = calendar.adjust(date, self._bus_day_adjust_type) adjusted_dates.append(adjusted) @@ -264,20 +285,18 @@ def _generate_adjusted_cds_payment_dates(self): # Accrual Start = [22-DEC-2008, 20-MAR-2009, 22-JUN-2009, 21-SEP-2009, 21-DEC-2009] # Accrual End = [19-MAR-2009, 21-JUN-2009, 20-SEP-2009, 20-DEC-2009, 20-MAR-2010] - self._payment_dates = adjusted_dates[1:] - - self._accrual_start_dates = adjusted_dates[:-1] - - self._accrual_end_dates = [date.add_days(-1) for date in self._accrual_start_dates[1:]] - self._accrual_end_dates.append(self._maturity_date) - elif self._date_gen_rule_type == DateGenRuleTypes.FORWARD: + # We start at start date and step forwards + next_date = start_date - while next_date < end_date: + # the unadjusted dates start at start date and end at last date before maturity date + while next_date < self._maturity_date: unadjusted_schedule_dates.append(next_date) next_date = next_date.add_months(num_months) + + # We then append the maturity date unadjusted_schedule_dates.append(self._maturity_date) adjusted_dates = [] @@ -290,16 +309,20 @@ def _generate_adjusted_cds_payment_dates(self): # Accrual Start = [20-FEB-2009, 20-MAY-2009, 20-AUG-2009, 20-NOV-2009] # Accrual End = [19-MAY-2009, 19-AUG-2009, 19-NOV-2009, 20-MAR-2010] - self._payment_dates = adjusted_dates[1:] + else: + raise FinError("Unknown DateGenRuleType:" + str(self._date_gen_rule_type)) - self._accrual_start_dates = adjusted_dates[:-1] + # We only include dates which fall after the CDS start date + self._payment_dates = adjusted_dates[1:] - self._accrual_end_dates = [date.add_days(-1) for date in self._accrual_start_dates[1:]] - self._accrual_end_dates.append(self._maturity_date) + # Accrual start dates run from previous coupon date to penultimate coupon date + self._accrual_start_dates = adjusted_dates[:-1] - else: - raise FinError("Unknown DateGenRuleType:" + - str(self._date_gen_rule_type)) + # Accrual end dates are one day before the start of the next accrual period + self._accrual_end_dates = [date.add_days(-1) for date in self._accrual_start_dates[1:]] + + # Final accrual end date is the maturity date + self._accrual_end_dates.append(self._maturity_date) ############################################################################### @@ -330,7 +353,7 @@ def value(self, contract_recovery_rate, pv01_method=0, prot_method=0, - num_steps_per_year=25): + num_steps_per_year=glob_num_steps_per_year): """ Valuation of a CDS contract on a specific valuation date given an issuer curve and a contract recovery rate.""" @@ -359,7 +382,7 @@ def value(self, cleanPV = fwdDf * longProt * \ (prot_pv - self._running_coupon * cleanRPV01 * self._notional) - # print("protLeg", prot_pv, "cleanRPV01", cleanRPV01, "value", cleanPV) +# print("protLeg", prot_pv, "cleanRPV01", cleanRPV01, "value", cleanPV) return {'full_pv': fullPV, 'clean_pv': cleanPV} @@ -371,7 +394,7 @@ def credit_dv01(self, contract_recovery_rate, pv01_method=0, prot_method=0, - num_steps_per_year=25): + num_steps_per_year=glob_num_steps_per_year): """ Calculation of the change in the value of the CDS contract for a one basis point change in the level of the CDS curve.""" @@ -409,7 +432,7 @@ def interest_dv01(self, contract_recovery_rate, pv01_method: int = 0, prot_method: int = 0, - num_steps_per_year: int = 25): + num_steps_per_year=glob_num_steps_per_year): """ Calculation of the interest DV01 based on a simple bump of the discount factors and reconstruction of the CDS curve. """ @@ -466,7 +489,7 @@ def cash_settlement_amount(self, contract_recovery_rate, pv01_method=0, prot_method=0, - num_steps_per_year=25): + num_steps_per_year=glob_num_steps_per_year): """ Value of the contract on the settlement date including accrued interest. """ @@ -490,7 +513,7 @@ def clean_price(self, contract_recovery_rate, pv01_method=0, prot_method=0, - num_steps_per_year=52): + num_steps_per_year=glob_num_steps_per_year): """ Value of the CDS contract excluding accrued interest. """ risky_pv01 = self.risky_pv01(valuation_date, issuer_curve, pv01_method) @@ -634,7 +657,7 @@ def protection_leg_pv(self, valuation_date, issuer_curve, contract_recovery_rate=standard_recovery_rate, - num_steps_per_year=25, + num_steps_per_year=glob_num_steps_per_year, protMethod=0): """ Calculates the protection leg PV of the CDS by calling into the fast NUMBA code that has been defined above. """ @@ -670,7 +693,9 @@ def risky_pv01(self, paymentTimes = [] for date in self._payment_dates: t = (date - valuation_date) / gDaysInYear - paymentTimes.append(t) # TODO: if t>0 + + if t > 0.0: + paymentTimes.append(t) # this is the part of the coupon accrued from the previous coupon date # to now @@ -720,7 +745,7 @@ def par_spread(self, valuation_date, issuer_curve, contract_recovery_rate=standard_recovery_rate, - num_steps_per_year=25, + num_steps_per_year=glob_num_steps_per_year, pv01_method=0, protMethod=0): """ Breakeven CDS coupon that would make the value of the CDS contract @@ -819,20 +844,22 @@ def value_fast_approx(self, ############################################################################### - def print_flows(self, issuer_curve): - - num_flows = len(self._adjusted_dates) + def print_flows(self, valuation_date, issuer_curve): + ''' We only print flows after the current valuation date ''' + num_flows = len(self._payment_dates) print("PAYMENT_DATE YEAR_FRAC FLOW DF SURV_PROB NPV") - for it in range(1, num_flows): - dt = self._adjusted_dates[it] - acc_factor = self._accrual_factors[it] - flow = self._flows[it] - z = issuer_curve.df(dt) - q = issuer_curve.survival_prob(dt) - print("%15s %10.6f %12.2f %12.6f %12.6f %12.2f" % - (dt, acc_factor, flow, z, q, flow * z * q)) + for it in range(0, num_flows): + dt = self._payment_dates[it] + + if dt > valuation_date: + acc_factor = self._accrual_factors[it] + flow = self._flows[it] + z = issuer_curve.df(dt) + q = issuer_curve.survival_prob(dt) + print("%15s %10.6f %12.2f %12.6f %12.6f %12.2f" % + (dt, acc_factor, flow, z, q, flow * z * q)) ############################################################################### @@ -852,13 +879,13 @@ def __repr__(self): s += label_to_string("DATEGENRULE", self._date_gen_rule_type) s += label_to_string("ACCRUED DAYS", self.accrued_days()) - header = "PAYMENT_DATE, YEAR_FRAC, FLOW, ACCRUAL_START, ACCTUAL_END" + header = "PAYMENT_DATE, YEAR_FRAC, ACCRUAL_START, ACCRUAL_END, FLOW" valueTable = [ self._payment_dates, self._accrual_factors, - self._flows, self._accrual_start_dates, - self._accrual_end_dates + self._accrual_end_dates, + self._flows ] precision = "12.6f" diff --git a/financepy/products/credit/cds_basket.py b/financepy/products/credit/cds_basket.py index b16feb57..a871b731 100644 --- a/financepy/products/credit/cds_basket.py +++ b/financepy/products/credit/cds_basket.py @@ -89,23 +89,25 @@ def value_legs_mc(self, num_credits = default_times.shape[0] num_trials = default_times.shape[1] - adjusted_dates = self._cds_contract._adjusted_dates - num_flows = len(adjusted_dates) + payment_dates = self._cds_contract._payment_dates + num_payments = len(payment_dates) day_count = DayCount(self._day_count_type) averageAccrualFactor = 0.0 - rpv01ToTimes = np.zeros(num_flows) - for iTime in range(1, num_flows): - t = (adjusted_dates[iTime] - valuation_date) / gDaysInYear - dt0 = adjusted_dates[iTime - 1] - dt1 = adjusted_dates[iTime] + rpv01ToTimes = np.zeros(num_payments) + + for iTime in range(1, num_payments): + + t = (payment_dates[iTime] - valuation_date) / gDaysInYear + dt0 = payment_dates[iTime - 1] + dt1 = payment_dates[iTime] accrual_factor = day_count.year_frac(dt0, dt1)[0] averageAccrualFactor += accrual_factor rpv01ToTimes[iTime] = rpv01ToTimes[iTime - 1] + \ accrual_factor * libor_curve._df(t) - averageAccrualFactor /= num_flows + averageAccrualFactor /= num_payments tmat = (self._maturity_date - valuation_date) / gDaysInYear @@ -115,7 +117,9 @@ def value_legs_mc(self, assetTau = np.zeros(num_credits) for iTrial in range(0, num_trials): + for iCredit in range(0, num_credits): + assetTau[iCredit] = default_times[iCredit, iTrial] # ORDER THE DEFAULT TIMES @@ -125,6 +129,7 @@ def value_legs_mc(self, minTau = assetTau[nToDefault - 1] if minTau < tmat: + numPaymentsIndex = int(minTau / averageAccrualFactor) rpv01Trial = rpv01ToTimes[numPaymentsIndex] rpv01Trial += (minTau - numPaymentsIndex * @@ -143,7 +148,7 @@ def value_legs_mc(self, else: numPaymentsIndex = int(tmat / averageAccrualFactor) - rpv01Trial = rpv01ToTimes[numPaymentsIndex] + rpv01Trial = rpv01ToTimes[-1] protTrial = 0.0 rpv01 += rpv01Trial @@ -255,7 +260,7 @@ def value_1f_gaussian_homo(self, if tmat < 0.0: raise FinError("Value date is after maturity date") - payment_dates = self._cds_contract._adjusted_dates + payment_dates = self._cds_contract._payment_dates num_times = len(payment_dates) issuerSurvivalProbabilities = np.zeros(num_credits) @@ -266,7 +271,7 @@ def value_1f_gaussian_homo(self, basketTimes[0] = 0.0 basketSurvivalCurve[0] = 1.0 - for iTime in range(1, num_times): + for iTime in range(0, num_times): t = (payment_dates[iTime] - valuation_date) / gDaysInYear @@ -331,7 +336,7 @@ def __repr__(self): s += label_to_string("DATEGENRULE", self._date_gen_rule_type) # header = "PAYMENT_DATE, YEAR_FRAC, FLOW" -# valueTable = [self._adjusted_dates, self._accrual_factors, self._flows] +# valueTable = [self._payment_dates, self._accrual_factors, self._flows] # precision = "12.6f" # s += tableToString(header, valueTable, precision) diff --git a/financepy/products/credit/cds_index_option.py b/financepy/products/credit/cds_index_option.py index 251cac9d..1852dc7d 100644 --- a/financepy/products/credit/cds_index_option.py +++ b/financepy/products/credit/cds_index_option.py @@ -291,7 +291,7 @@ def _calc_index_payer_option_price(self, dz = 0.2 numZSteps = int(2.0 * abs(z) / dz) - flow_dates = self._cds_contract._adjusted_dates + flow_dates = self._cds_contract._payment_dates num_flows = len(flow_dates) texp = (self._expiry_date - valuation_date) / gDaysInYear dfToExpiry = libor_curve.df(self._expiry_date) @@ -300,16 +300,21 @@ def _calc_index_payer_option_price(self, fwdDfs = [1.0] * (num_flows) expiryToFlowTimes = [1.0] * (num_flows) - for iFlow in range(1, num_flows): - expiryToFlowTimes[iFlow] = ( - flow_dates[iFlow] - self._expiry_date) / gDaysInYear + for iFlow in range(0, num_flows): + expiryToFlowTimes[iFlow] = (flow_dates[iFlow] - self._expiry_date) / gDaysInYear fwdDfs[iFlow] = libor_curve.df(flow_dates[iFlow]) / dfToExpiry intH = 0.0 intMaxH = 0.0 day_count = DayCount(self._day_count_type) - pcd = flow_dates[0] # PCD + + # Previous coupon date is last coupon date before valuation date + for dt in flow_dates: + pcd = dt + if dt > valuation_date: + break + eff = self._expiry_date accrual_factorPCDToExpiry = day_count.year_frac(pcd, eff)[0] diff --git a/financepy/products/credit/cds_index_portfolio.py b/financepy/products/credit/cds_index_portfolio.py index c182f265..31ac4521 100644 --- a/financepy/products/credit/cds_index_portfolio.py +++ b/financepy/products/credit/cds_index_portfolio.py @@ -62,6 +62,7 @@ def intrinsic_rpv01(self, intrinsic_rpv01 = 0.0 for m in range(0, num_credits): + retValue = cds_contract.risky_pv01(valuation_date, issuer_curves[m]) @@ -87,8 +88,8 @@ def intrinsic_protection_leg_pv(self, intrinsic_prot_pv = 0.0 # All contracts have same flows so only need one object - cds_contract = CDS(step_in_date, - maturity_date, + cds_contract = CDS(step_in_date, + maturity_date, 0.0, 1.0) @@ -291,9 +292,9 @@ def spread_adjust_intrinsic(self, curveCDSContracts.append(cds_contract) ####################################################################### - # We calibrate the individual CDS discount to fit each index maturity - # point + ####################################################################### + for iMaturity in range(0, numIndexMaturityPoints): alpha = 0.0 @@ -321,9 +322,9 @@ def spread_adjust_intrinsic(self, adjustedCDSContracts = [] for j in range(0, numCDSMaturityPoints): + cdsSpread = cds_contracts[j]._running_coupon - adjustedCDSSpreads[j] = cdsSpread * \ - cdsSpreadMultipliers[j] + adjustedCDSSpreads[j] = cdsSpread * cdsSpreadMultipliers[j] curveCDSContracts[j]._running_coupon = adjustedCDSSpreads[j] adjustedIssuerCurve = CDSCurve(valuation_date, @@ -397,6 +398,16 @@ def hazard_rate_adjust_intrinsic(self, """ Adjust individual CDS discount to reprice CDS index prices. This approach adjusts the hazard rates and so avoids the slowish CDS curve bootstrap required when a spread adjustment is made.""" + + raise FinError("This Function is being repaired. Do not use.") + + print("=========================================") + print(valuation_date) + print(index_coupons) + print(index_up_fronts) + print(index_maturity_dates) + print(index_recovery_rate) + num_credits = len(issuer_curves) if num_credits < 1: @@ -439,6 +450,7 @@ def hazard_rate_adjust_intrinsic(self, sumProt = 0.0 for iCredit in range(0, num_credits): + q1 = adjusted_issuer_curves[iCredit]._values[iMaturity] q2 = adjusted_issuer_curves[iCredit]._values[iMaturity + 1] q12 = q2 / q1 @@ -449,17 +461,18 @@ def hazard_rate_adjust_intrinsic(self, index_maturity_date = index_maturity_dates[iMaturity] - # the CDS spreads we extract here should be the index - # maturity dates - cdsIndex = CDS(valuation_date, index_maturity_date, 0, 1.0) + # the CDS spreads we extract here + # should be the index maturity dates + cdsIndex = CDS(valuation_date, + index_maturity_date, + 0, 1.0) indexProtPV = cdsIndex.protection_leg_pv(valuation_date, adjusted_issuer_curves[iCredit], index_recovery_rate) - rpv01Ret = cdsIndex.risky_pv01( - valuation_date, - adjusted_issuer_curves[iCredit]) + rpv01Ret = cdsIndex.risky_pv01(valuation_date, + adjusted_issuer_curves[iCredit]) cleanRPV01 = rpv01Ret['clean_rpv01'] @@ -468,6 +481,7 @@ def hazard_rate_adjust_intrinsic(self, sumRPV01 /= num_credits sumProt /= num_credits + sumPrem = sumRPV01 * index_coupons[iMaturity] numerator = index_up_fronts[iMaturity] + sumPrem @@ -476,6 +490,10 @@ def hazard_rate_adjust_intrinsic(self, ratio = numerator / denominator alpha *= ratio + print("Maturity:", iMaturity, "Num:", numerator, "Den:", denominator, "Ratio:", ratio, "Alpha:", alpha) + + print("") + return adjusted_issuer_curves ########################################################################### diff --git a/financepy/products/credit/cds_tranche.py b/financepy/products/credit/cds_tranche.py index 991354e2..5ceb771e 100644 --- a/financepy/products/credit/cds_tranche.py +++ b/financepy/products/credit/cds_tranche.py @@ -126,8 +126,9 @@ def value_bc(self, recovery_rates = np.zeros(num_credits) - payment_dates = self._cds_contract._adjusted_dates - num_times = len(payment_dates) + payment_dates = self._cds_contract._payment_dates + num_payments = len(payment_dates) + num_times = num_payments + 1 beta1 = sqrt(corr1) beta2 = sqrt(corr2) @@ -140,8 +141,9 @@ def value_bc(self, beta_vector2[bb] = beta2 qVector = np.zeros(num_credits) - qt1 = np.zeros(num_times) - qt2 = np.zeros(num_times) + qt1 = np.zeros(num_times) # include 1.0 + qt2 = np.zeros(num_times) # include 1.0 + trancheTimes = np.zeros(num_times) trancheSurvivalCurve = np.zeros(num_times) @@ -152,9 +154,10 @@ def value_bc(self, for i in range(1, num_times): - t = (payment_dates[i] - valuation_date) / gDaysInYear + t = (payment_dates[i-1] - valuation_date) / gDaysInYear for j in range(0, num_credits): + issuer_curve = issuer_curves[j] vTimes = issuer_curve._times qRow = issuer_curve._values @@ -163,20 +166,25 @@ def value_bc(self, t, vTimes, qRow, InterpTypes.FLAT_FWD_RATES.value) if model == FinLossDistributionBuilder.RECURSION: + qt1[i] = tranche_surv_prob_recursion( 0.0, k1, num_credits, qVector, recovery_rates, beta_vector1, num_points) qt2[i] = tranche_surv_prob_recursion( 0.0, k2, num_credits, qVector, recovery_rates, beta_vector2, num_points) + elif model == FinLossDistributionBuilder.ADJUSTED_BINOMIAL: + qt1[i] = tranche_surv_prob_adj_binomial( 0.0, k1, num_credits, qVector, recovery_rates, beta_vector1, num_points) qt2[i] = tranche_surv_prob_adj_binomial( 0.0, k2, num_credits, qVector, recovery_rates, beta_vector2, num_points) + elif model == FinLossDistributionBuilder.GAUSSIAN: + qt1[i] = tranch_surv_prob_gaussian( 0.0, k1, @@ -193,11 +201,15 @@ def value_bc(self, recovery_rates, beta_vector2, num_points) + elif model == FinLossDistributionBuilder.LHP: + qt1[i] = tr_surv_prob_lhp( 0.0, k1, num_credits, qVector, recovery_rates, beta1) + qt2[i] = tr_surv_prob_lhp( 0.0, k2, num_credits, qVector, recovery_rates, beta2) + else: raise FinError( "Unknown model type only full and AdjBinomial allowed") diff --git a/financepy/products/inflation/FinInflationBond.py b/financepy/products/inflation/FinInflationBond.py index a709d88a..96d9a4b8 100644 --- a/financepy/products/inflation/FinInflationBond.py +++ b/financepy/products/inflation/FinInflationBond.py @@ -30,7 +30,7 @@ def __init__(self, coupon: float, # Annualised bond coupon before inflation freq_type: FrequencyTypes, accrual_type: DayCountTypes, - face_amount: float, + ex_div_days: int, base_cpi_value: float, num_ex_dividend_days: int = 0, calendar_type: CalendarTypes = CalendarTypes.NONE): # Value of CPI index at bond issue date @@ -39,7 +39,7 @@ def __init__(self, the base CPI used for all coupon and principal related calculations. The class inherits from Bond so has many similar functions. The YTM""" - Bond.__init__(self,issue_date, maturity_date, coupon, freq_type, accrual_type, face_amount, calendar_type) + Bond.__init__(self,issue_date, maturity_date, coupon, freq_type, accrual_type, ex_div_days, calendar_type) check_argument_types(self.__init__, locals()) @@ -58,7 +58,7 @@ def __init__(self, self._freq_type = freq_type self._accrual_type = accrual_type self._frequency = annual_frequency(freq_type) - self._face_amount = face_amount # This is the bond holding size + self._ex_div_days = ex_div_days # This is the bond holding size self._baseCPIValue = base_cpi_value # CPI value at issue date of bond self._par = 100.0 # This is how price is quoted self._redemption = 1.0 # Amount paid at maturity @@ -80,6 +80,7 @@ def __init__(self, def inflation_principal(self, settlement_date: Date, + face: (float), ytm: float, reference_cpi: float, convention: YTMCalcType): @@ -87,8 +88,8 @@ def inflation_principal(self, amount and the CPI growth. """ index_ratio = reference_cpi / self._baseCPIValue - full_price = self.full_price_from_ytm(settlement_date, ytm, convention) - principal = full_price * self._face_amount / self._par + full_price = self.dirty_price_from_ytm(settlement_date, ytm, convention) + principal = full_price * face / self._par principal = principal - self._accrued_interest principal *= index_ratio return principal @@ -106,13 +107,14 @@ def flat_price_from_yield_to_maturity(self, index_ratio = last_cpn_cpi / self._baseCPIValue clean_price = self.clean_price_from_ytm( settlement_date, ytm, convention) - flat_price = clean_price * self._face_amount / self._par + flat_price = clean_price flat_price *= index_ratio return flat_price ############################################################################### - def calc_inflation_accrued_interest(self, settlement_date: Date, + def inflation_accrued_interest(self, settlement_date: Date, + face: (float), reference_cpi): """ Calculate the amount of coupon that has accrued between the previous coupon date and the settlement date. This is adjusted by the @@ -120,8 +122,8 @@ def calc_inflation_accrued_interest(self, settlement_date: Date, We assume no ex-dividend period. """ - self.calc_accrued_interest(settlement_date) - index_ratio = reference_cpi/self._baseCPIValue + self.accrued_interest(settlement_date, face) + index_ratio = reference_cpi / self._baseCPIValue self._inflation_accrued_interest = self._accrued_interest * index_ratio return self._inflation_accrued_interest @@ -134,7 +136,7 @@ def __repr__(self): s += label_to_string("COUPON", self._coupon) s += label_to_string("FREQUENCY", self._freq_type) s += label_to_string("ACCRUAL TYPE", self._accrual_type) - s += label_to_string("FACE AMOUNT", self._face_amount) + s += label_to_string("EX-DIV DAYS", self._ex_div_days) s += label_to_string("BASE CPI VALUE", self._baseCPIValue, "") return s diff --git a/financepy/utils/date.py b/financepy/utils/date.py index 7e1fab72..989f851d 100644 --- a/financepy/utils/date.py +++ b/financepy/utils/date.py @@ -313,13 +313,23 @@ def from_string(cls, date_string, formatString): ########################################################################### @classmethod - def from_date(cls, date: datetime.date): - """ Create a Date from a python datetime.date object. + def from_date(cls, date: [datetime.date, np.datetime64]): + """ Create a Date from a python datetime.date object or from a + Numpy datetime64 object. Example Input: start_date = Date.from_date(datetime.date(2022, 11, 8)) """ - d, m, y = date.day, date.month, date.year - return cls(d, m, y) + if isinstance(date, datetime.date): + d, m, y = date.day, date.month, date.year + return cls(d, m, y) + + if isinstance(date, np.datetime64): + timestamp = ((date - np.datetime64('1970-01-01T00:00:00')) + / np.timedelta64(1, 's')) + + date = datetime.datetime.utcfromtimestamp(timestamp) + d, m, y = date.day, date.month, date.year + return cls(d, m, y) ########################################################################### def _refresh(self): diff --git a/notebooks/products/credit/FINCDS_ComparisonWithMarkitCDSModel.ipynb b/notebooks/products/credit/FINCDS_ComparisonWithMarkitCDSModel.ipynb index a6b053aa..1b71327a 100644 --- a/notebooks/products/credit/FINCDS_ComparisonWithMarkitCDSModel.ipynb +++ b/notebooks/products/credit/FINCDS_ComparisonWithMarkitCDSModel.ipynb @@ -259,14 +259,18 @@ }, { "cell_type": "code", - "execution_count": 18, + "execution_count": 1, "metadata": {}, "outputs": [ { - "name": "stdout", - "output_type": "stream", - "text": [ - "FAIR CDS SPREAD 100.08998 bp\n" + "ename": "NameError", + "evalue": "name 'cds_contract' is not defined", + "output_type": "error", + "traceback": [ + "\u001b[1;31m---------------------------------------------------------------------------\u001b[0m", + "\u001b[1;31mNameError\u001b[0m Traceback (most recent call last)", + "Cell \u001b[1;32mIn[1], line 1\u001b[0m\n\u001b[1;32m----> 1\u001b[0m spd \u001b[39m=\u001b[39m cds_contract\u001b[39m.\u001b[39mpar_spread(settlement_date, issuer_curve, recovery_rate) \u001b[39m*\u001b[39m \u001b[39m10000.0\u001b[39m\n\u001b[0;32m 2\u001b[0m \u001b[39mprint\u001b[39m(\u001b[39m\"\u001b[39m\u001b[39mFAIR CDS SPREAD \u001b[39m\u001b[39m%10.5f\u001b[39;00m\u001b[39m bp\u001b[39m\u001b[39m\"\u001b[39m\u001b[39m%\u001b[39m spd)\n", + "\u001b[1;31mNameError\u001b[0m: name 'cds_contract' is not defined" ] } ], @@ -514,7 +518,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.8.5" + "version": "3.10.9" } }, "nbformat": 4, diff --git a/notebooks/products/credit/FINCDS_ComparisonWithMarkitCDSModelV2.ipynb b/notebooks/products/credit/FINCDS_ComparisonWithMarkitCDSModelV2.ipynb new file mode 100644 index 00000000..40ec88f7 --- /dev/null +++ b/notebooks/products/credit/FINCDS_ComparisonWithMarkitCDSModelV2.ipynb @@ -0,0 +1,773 @@ +{ + "cells": [ + { + "attachments": {}, + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Creating and Valuing a CDS Contract" + ] + }, + { + "attachments": {}, + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Replicating Markit Pricing on 20 Aug 2020" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "metadata": {}, + "outputs": [], + "source": [ + "import numpy as np\n", + "import matplotlib.pyplot as plt" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "####################################################################\n", + "# FINANCEPY BETA Version 0.300 - This build: 04 Jul 2023 at 14:15 #\n", + "# This software is distributed FREE AND WITHOUT ANY WARRANTY #\n", + "# Report bugs as issues at https://github.com/domokane/FinancePy #\n", + "####################################################################\n", + "\n" + ] + } + ], + "source": [ + "from financepy.utils import *\n", + "from financepy.products.rates import *\n", + "from financepy.products.credit import *" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": {}, + "outputs": [], + "source": [ + "valuation_date = Date(20, 8, 2020)" + ] + }, + { + "attachments": {}, + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Build Ibor Curve" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "metadata": {}, + "outputs": [], + "source": [ + "settlement_date = Date(24, 8, 2020)\n" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "metadata": {}, + "outputs": [], + "source": [ + "dcType = DayCountTypes.ACT_360\n", + "depo1 = IborDeposit(settlement_date, \"1M\", 0.001709, dcType)\n", + "depo2 = IborDeposit(settlement_date, \"2M\", 0.002123, dcType)\n", + "depo3 = IborDeposit(settlement_date, \"3M\", 0.002469, dcType)\n", + "depo4 = IborDeposit(settlement_date, \"6M\", 0.003045, dcType)\n", + "depo5 = IborDeposit(settlement_date, \"12M\", 0.004449, dcType)\n", + "depos = [depo1,depo2,depo3,depo4,depo5]\n", + "\n", + "swapType = SwapTypes.PAY\n", + "dcType = DayCountTypes.THIRTY_E_360_ISDA\n", + "fixedFreq = FrequencyTypes.SEMI_ANNUAL\n", + "swap1 = IborSwap(settlement_date,\"2Y\", swapType, 0.002155,fixedFreq,dcType)\n", + "swap2 = IborSwap(settlement_date,\"3Y\", swapType, 0.002305,fixedFreq,dcType)\n", + "swap3 = IborSwap(settlement_date,\"4Y\", swapType, 0.002665,fixedFreq,dcType)\n", + "swap4 = IborSwap(settlement_date,\"5Y\", swapType, 0.003290,fixedFreq,dcType)\n", + "swap5 = IborSwap(settlement_date,\"6Y\", swapType, 0.004025,fixedFreq,dcType)\n", + "swap6 = IborSwap(settlement_date,\"7Y\", swapType, 0.004725,fixedFreq,dcType)\n", + "swap7 = IborSwap(settlement_date,\"8Y\", swapType, 0.005430,fixedFreq,dcType)\n", + "swap8 = IborSwap(settlement_date,\"9Y\", swapType, 0.006075,fixedFreq,dcType)\n", + "swap9 = IborSwap(settlement_date,\"10Y\", swapType, 0.006640,fixedFreq,dcType)\n", + "swaps = [swap1,swap2,swap3,swap4,swap5,swap6,swap7,swap8,swap9]\n", + "\n", + "libor_curve = IborSingleCurve(valuation_date, depos, [], swaps)" + ] + }, + { + "attachments": {}, + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Creating a CDS Contract" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "metadata": {}, + "outputs": [], + "source": [ + "effective_date = Date(20, 6, 2018)\n", + "maturity_date = Date(20, 6, 2025)\n", + "running_coupon = 0.05\n", + "notional = ONE_MILLION\n", + "long_protection = True" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "metadata": {}, + "outputs": [], + "source": [ + "cds_contract = CDS(effective_date, maturity_date, running_coupon, notional, long_protection)" + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "OBJECT TYPE: CDS\n", + "STEP-IN DATE: 20-JUN-2018\n", + "MATURITY: 20-JUN-2025\n", + "NOTIONAL: 1000000\n", + "RUNNING COUPON: 500.0bp\n", + "DAYCOUNT: DayCountTypes.ACT_360\n", + "FREQUENCY: FrequencyTypes.QUARTERLY\n", + "CALENDAR: CalendarTypes.WEEKEND\n", + "BUSDAYRULE: BusDayAdjustTypes.FOLLOWING\n", + "DATEGENRULE: DateGenRuleTypes.BACKWARD\n", + "ACCRUED DAYS: 0.0\n", + "PAYMENT_DATE, YEAR_FRAC, ACCRUAL_START, ACCRUAL_END, FLOW\n", + "20-SEP-2018, 0.255556, 20-JUN-2018, 19-SEP-2018, 12777.777778\n", + "20-DEC-2018, 0.252778, 20-SEP-2018, 19-DEC-2018, 12638.888889\n", + "20-MAR-2019, 0.250000, 20-DEC-2018, 19-MAR-2019, 12500.000000\n", + "20-JUN-2019, 0.255556, 20-MAR-2019, 19-JUN-2019, 12777.777778\n", + "20-SEP-2019, 0.255556, 20-JUN-2019, 19-SEP-2019, 12777.777778\n", + "20-DEC-2019, 0.252778, 20-SEP-2019, 19-DEC-2019, 12638.888889\n", + "20-MAR-2020, 0.252778, 20-DEC-2019, 19-MAR-2020, 12638.888889\n", + "22-JUN-2020, 0.261111, 20-MAR-2020, 21-JUN-2020, 13055.555556\n", + "21-SEP-2020, 0.252778, 22-JUN-2020, 20-SEP-2020, 12638.888889\n", + "21-DEC-2020, 0.252778, 21-SEP-2020, 20-DEC-2020, 12638.888889\n", + "22-MAR-2021, 0.252778, 21-DEC-2020, 21-MAR-2021, 12638.888889\n", + "21-JUN-2021, 0.252778, 22-MAR-2021, 20-JUN-2021, 12638.888889\n", + "20-SEP-2021, 0.252778, 21-JUN-2021, 19-SEP-2021, 12638.888889\n", + "20-DEC-2021, 0.252778, 20-SEP-2021, 19-DEC-2021, 12638.888889\n", + "21-MAR-2022, 0.252778, 20-DEC-2021, 20-MAR-2022, 12638.888889\n", + "20-JUN-2022, 0.252778, 21-MAR-2022, 19-JUN-2022, 12638.888889\n", + "20-SEP-2022, 0.255556, 20-JUN-2022, 19-SEP-2022, 12777.777778\n", + "20-DEC-2022, 0.252778, 20-SEP-2022, 19-DEC-2022, 12638.888889\n", + "20-MAR-2023, 0.250000, 20-DEC-2022, 19-MAR-2023, 12500.000000\n", + "20-JUN-2023, 0.255556, 20-MAR-2023, 19-JUN-2023, 12777.777778\n", + "20-SEP-2023, 0.255556, 20-JUN-2023, 19-SEP-2023, 12777.777778\n", + "20-DEC-2023, 0.252778, 20-SEP-2023, 19-DEC-2023, 12638.888889\n", + "20-MAR-2024, 0.252778, 20-DEC-2023, 19-MAR-2024, 12638.888889\n", + "20-JUN-2024, 0.255556, 20-MAR-2024, 19-JUN-2024, 12777.777778\n", + "20-SEP-2024, 0.255556, 20-JUN-2024, 19-SEP-2024, 12777.777778\n", + "20-DEC-2024, 0.252778, 20-SEP-2024, 19-DEC-2024, 12638.888889\n", + "20-MAR-2025, 0.250000, 20-DEC-2024, 19-MAR-2025, 12500.000000\n", + "20-JUN-2025, 0.258333, 20-MAR-2025, 20-JUN-2025, 12916.666667\n" + ] + } + ], + "source": [ + "print(cds_contract)" + ] + }, + { + "attachments": {}, + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Build a CDS Curve" + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "metadata": {}, + "outputs": [], + "source": [ + "cdsSpread = 0.01" + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "metadata": {}, + "outputs": [], + "source": [ + "cds1 = CDS(settlement_date, \"6M\", cdsSpread)\n", + "cds2 = CDS(settlement_date, \"1Y\", cdsSpread)\n", + "cds3 = CDS(settlement_date, \"2Y\", cdsSpread)\n", + "cds4 = CDS(settlement_date, \"3Y\", cdsSpread)\n", + "cds5 = CDS(settlement_date, \"4Y\", cdsSpread)\n", + "cds6 = CDS(settlement_date, \"5Y\", cdsSpread)\n", + "cds7 = CDS(settlement_date, \"7Y\", cdsSpread)\n", + "cds8 = CDS(settlement_date, \"10Y\", cdsSpread)" + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "metadata": {}, + "outputs": [], + "source": [ + "cdss = [cds1, cds2, cds3] #, cds4, cds5, cds6, cds7, cds8]" + ] + }, + { + "cell_type": "code", + "execution_count": 12, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "OBJECT TYPE: CDS\n", + "STEP-IN DATE: 24-AUG-2020\n", + "MATURITY: 20-SEP-2022\n", + "NOTIONAL: 1000000\n", + "RUNNING COUPON: 100.0bp\n", + "DAYCOUNT: DayCountTypes.ACT_360\n", + "FREQUENCY: FrequencyTypes.QUARTERLY\n", + "CALENDAR: CalendarTypes.WEEKEND\n", + "BUSDAYRULE: BusDayAdjustTypes.FOLLOWING\n", + "DATEGENRULE: DateGenRuleTypes.BACKWARD\n", + "ACCRUED DAYS: 63.0\n", + "PAYMENT_DATE, YEAR_FRAC, ACCRUAL_START, ACCRUAL_END, FLOW\n", + "21-SEP-2020, 0.252778, 22-JUN-2020, 20-SEP-2020, 2527.777778\n", + "21-DEC-2020, 0.252778, 21-SEP-2020, 20-DEC-2020, 2527.777778\n", + "22-MAR-2021, 0.252778, 21-DEC-2020, 21-MAR-2021, 2527.777778\n", + "21-JUN-2021, 0.252778, 22-MAR-2021, 20-JUN-2021, 2527.777778\n", + "20-SEP-2021, 0.252778, 21-JUN-2021, 19-SEP-2021, 2527.777778\n", + "20-DEC-2021, 0.252778, 20-SEP-2021, 19-DEC-2021, 2527.777778\n", + "21-MAR-2022, 0.252778, 20-DEC-2021, 20-MAR-2022, 2527.777778\n", + "20-JUN-2022, 0.252778, 21-MAR-2022, 19-JUN-2022, 2527.777778\n", + "20-SEP-2022, 0.258333, 20-JUN-2022, 20-SEP-2022, 2583.333333\n" + ] + } + ], + "source": [ + "print(cds3)" + ] + }, + { + "cell_type": "code", + "execution_count": 13, + "metadata": {}, + "outputs": [], + "source": [ + "recovery_rate = 0.40" + ] + }, + { + "cell_type": "code", + "execution_count": 14, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "===================\n", + "Teff 0.010958904109589041\n", + "Acc 0.175\n", + "Payments [0.08767123 0.3369863 0.58630137]\n", + "Alphas [0.25277778 0.25277778 0.25 ]\n", + "QTimes [0. 0.58082192]\n", + "QValues [1. 1.]\n", + "ProtPV 0.010958904109589041 0.5808219178082191 0.0\n", + "protLeg 0.0 cleanRPV01 0.5797856809030655 value -5797.856809030654\n", + "===================\n", + "Teff 0.010958904109589041\n", + "Acc 0.175\n", + "Payments [0.08767123 0.3369863 0.58630137]\n", + "Alphas [0.25277778 0.25277778 0.25 ]\n", + "QTimes [0. 0.58082192]\n", + "QValues [1. 1.0002]\n", + "ProtPV 0.010958904109589041 0.5808219178082191 -0.00011763840860145024\n", + "protLeg -117.63840860145024 cleanRPV01 0.5798457760111959 value -5916.0961687134095\n", + "===================\n", + "Teff 0.010958904109589041\n", + "Acc 0.175\n", + "Payments [0.08767123 0.3369863 0.58630137]\n", + "Alphas [0.25277778 0.25277778 0.25 ]\n", + "QTimes [0. 0.58082192]\n", + "QValues [1. 0.99019302]\n", + "ProtPV 0.010958904109589041 0.5808219178082191 0.005767869195283966\n", + "protLeg 5767.869195283966 cleanRPV01 0.5768339663830966 value -0.4704685469996548\n", + "===================\n", + "Teff 0.010958904109589041\n", + "Acc 0.175\n", + "Payments [0.08767123 0.3369863 0.58630137]\n", + "Alphas [0.25277778 0.25277778 0.25 ]\n", + "QTimes [0. 0.58082192]\n", + "QValues [1. 0.99019222]\n", + "ProtPV 0.010958904109589041 0.5808219178082191 0.005768337224745482\n", + "protLeg 5768.337224745482 cleanRPV01 0.5768337264502386 value -3.975690378865693e-05\n", + "===================\n", + "Teff 0.010958904109589041\n", + "Acc 0.175\n", + "Payments [0.08767123 0.3369863 0.58630137 0.83561644 1.08493151]\n", + "Alphas [0.25277778 0.25277778 0.25277778 0.25277778 0.25555556]\n", + "QTimes [0. 0.58082192 1.08493151]\n", + "QValues [1. 0.99019222 0.99019222]\n", + "ProtPV 0.010958904109589041 1.084931506849315 0.0057683307957754\n", + "protLeg 5768.3307957754 cleanRPV01 1.0809372082376678 value -5041.041286601278\n", + "===================\n", + "Teff 0.010958904109589041\n", + "Acc 0.175\n", + "Payments [0.08767123 0.3369863 0.58630137 0.83561644 1.08493151]\n", + "Alphas [0.25277778 0.25277778 0.25277778 0.25277778 0.25555556]\n", + "QTimes [0. 0.58082192 1.08493151]\n", + "QValues [1. 0.99019222 0.99039124]\n", + "ProtPV 0.010958904109589041 1.084931506849315 0.005668810556198163\n", + "protLeg 5668.810556198163 cleanRPV01 1.0809885010497093 value -5141.0744542989305\n", + "===================\n", + "Teff 0.010958904109589041\n", + "Acc 0.175\n", + "Payments [0.08767123 0.3369863 0.58630137 0.83561644 1.08493151]\n", + "Alphas [0.25277778 0.25277778 0.25277778 0.25277778 0.25555556]\n", + "QTimes [0. 0.58082192 1.08493151]\n", + "QValues [1. 0.99019222 0.98016291]\n", + "ProtPV 0.010958904109589041 1.084931506849315 0.011765357732633135\n", + "protLeg 11765.357732633136 cleanRPV01 1.0783478627758178 value 981.879104874959\n", + "===================\n", + "Teff 0.010958904109589041\n", + "Acc 0.175\n", + "Payments [0.08767123 0.3369863 0.58630137 0.83561644 1.08493151]\n", + "Alphas [0.25277778 0.25277778 0.25277778 0.25277778 0.25555556]\n", + "QTimes [0. 0.58082192 1.08493151]\n", + "QValues [1. 0.99019222 0.98180313]\n", + "ProtPV 0.010958904109589041 1.084931506849315 0.010784586546707726\n", + "protLeg 10784.586546707727 cleanRPV01 1.0787719385933483 value -3.132839225754651\n", + "===================\n", + "Teff 0.010958904109589041\n", + "Acc 0.175\n", + "Payments [0.08767123 0.3369863 0.58630137 0.83561644 1.08493151]\n", + "Alphas [0.25277778 0.25277778 0.25277778 0.25277778 0.25555556]\n", + "QTimes [0. 0.58082192 1.08493151]\n", + "QValues [1. 0.99019222 0.98179791]\n", + "ProtPV 0.010958904109589041 1.084931506849315 0.010787705896900103\n", + "protLeg 10787.705896900103 cleanRPV01 1.0787705901947784 value -5.047681042924523e-06\n", + "===================\n", + "Teff 0.010958904109589041\n", + "Acc 0.175\n", + "Payments [0.08767123 0.3369863 0.58630137 0.83561644 1.08493151 1.33424658\n", + " 1.58356164 1.83287671 2.08493151]\n", + "Alphas [0.25277778 0.25277778 0.25277778 0.25277778 0.25277778 0.25277778\n", + " 0.25277778 0.25277778 0.25833333]\n", + "QTimes [0. 0.58082192 1.08493151 2.08493151]\n", + "QValues [1. 0.99019222 0.98179791 0.98179791]\n", + "ProtPV 0.010958904109589041 2.084931506849315 0.01078770780810252\n", + "protLeg 10787.70780810252 cleanRPV01 2.0697949876519037 value -9910.242068416517\n", + "===================\n", + "Teff 0.010958904109589041\n", + "Acc 0.175\n", + "Payments [0.08767123 0.3369863 0.58630137 0.83561644 1.08493151 1.33424658\n", + " 1.58356164 1.83287671 2.08493151]\n", + "Alphas [0.25277778 0.25277778 0.25277778 0.25277778 0.25277778 0.25277778\n", + " 0.25277778 0.25277778 0.25833333]\n", + "QTimes [0. 0.58082192 1.08493151 2.08493151]\n", + "QValues [1. 0.99019222 0.98179791 0.98199609]\n", + "ProtPV 0.010958904109589041 2.084931506849315 0.010881146230010847\n", + "protLeg 10881.146230010847 cleanRPV01 2.069895448260758 value -9817.808252596733\n", + "===================\n", + "Teff 0.010958904109589041\n", + "Acc 0.175\n", + "Payments [0.08767123 0.3369863 0.58630137 0.83561644 1.08493151 1.33424658\n", + " 1.58356164 1.83287671 2.08493151]\n", + "Alphas [0.25277778 0.25277778 0.25277778 0.25277778 0.25277778 0.25277778\n", + " 0.25277778 0.25277778 0.25833333]\n", + "QTimes [0. 0.58082192 1.08493151 2.08493151]\n", + "QValues [1. 0.99019222 0.98179791 1.00304565]\n", + "ProtPV 0.010958904109589041 2.084931506849315 0.023150287643578953\n", + "protLeg 23150.28764357895 cleanRPV01 2.080527845166699 value 2345.0091919119623\n", + "===================\n", + "Teff 0.010958904109589041\n", + "Acc 0.175\n", + "Payments [0.08767123 0.3369863 0.58630137 0.83561644 1.08493151 1.33424658\n", + " 1.58356164 1.83287671 2.08493151]\n", + "Alphas [0.25277778 0.25277778 0.25277778 0.25277778 0.25277778 0.25277778\n", + " 0.25277778 0.25277778 0.25833333]\n", + "QTimes [0. 0.58082192 1.08493151 2.08493151]\n", + "QValues [1. 0.99019222 0.98179791 0.99897995]\n", + "ProtPV 0.010958904109589041 2.084931506849315 0.020721612490222727\n", + "protLeg 20721.612490222728 cleanRPV01 2.078480021669217 value -63.18772646944126\n", + "===================\n", + "Teff 0.010958904109589041\n", + "Acc 0.175\n", + "Payments [0.08767123 0.3369863 0.58630137 0.83561644 1.08493151 1.33424658\n", + " 1.58356164 1.83287671 2.08493151]\n", + "Alphas [0.25277778 0.25277778 0.25277778 0.25277778 0.25277778 0.25277778\n", + " 0.25277778 0.25277778 0.25833333]\n", + "QTimes [0. 0.58082192 1.08493151 2.08493151]\n", + "QValues [1. 0.99019222 0.98179791 0.99908663]\n", + "ProtPV 0.010958904109589041 2.084931506849315 0.02078533753379403\n", + "protLeg 20785.33753379403 cleanRPV01 2.078533789521561 value -0.00036142158205620944\n" + ] + } + ], + "source": [ + "issuer_curve = CDSCurve(valuation_date, cdss, libor_curve, recovery_rate)" + ] + }, + { + "cell_type": "code", + "execution_count": 15, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "OBJECT TYPE: CDSCurve\n", + "TIME,SURVIVAL_PROBABILITY\n", + " 0.0000000, 1.0000000\n", + " 0.5808219, 0.9901922\n", + " 1.0849315, 0.9817979\n", + " 2.0849315, 0.9990866\n" + ] + } + ], + "source": [ + "print(issuer_curve)" + ] + }, + { + "cell_type": "code", + "execution_count": 16, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "PAYMENT_DATE YEAR_FRAC FLOW DF SURV_PROB NPV\n", + " 21-SEP-2020 0.252778 12638.89 0.999848 0.998513 12618.18\n", + " 21-DEC-2020 0.252778 12638.89 0.999079 0.994298 12555.25\n", + " 22-MAR-2021 0.252778 12638.89 0.998005 0.990101 12488.80\n", + " 21-JUN-2021 0.252778 12638.89 0.996528 0.985941 12417.93\n", + " 20-SEP-2021 0.252778 12638.89 0.995505 0.981798 12353.06\n", + " 20-DEC-2021 0.252778 12638.89 0.995554 0.986080 12407.55\n", + " 21-MAR-2022 0.252778 12638.89 0.995603 0.990381 12462.28\n", + " 20-JUN-2022 0.252778 12638.89 0.995652 0.994700 12517.25\n", + " 20-SEP-2022 0.255556 12777.78 0.995495 0.999087 12708.60\n", + " 20-DEC-2022 0.252778 12638.89 0.994848 1.003444 12617.08\n", + " 20-MAR-2023 0.250000 12500.00 0.994209 1.007773 12524.21\n", + " 20-JUN-2023 0.255556 12777.78 0.993556 1.012216 12850.53\n", + " 20-SEP-2023 0.255556 12777.78 0.992820 1.016680 12897.64\n", + " 20-DEC-2023 0.252778 12638.89 0.991895 1.021114 12801.15\n", + " 20-MAR-2024 0.252778 12638.89 0.990971 1.025568 12845.00\n", + " 20-JUN-2024 0.255556 12777.78 0.990038 1.030090 13031.13\n", + " 20-SEP-2024 0.255556 12777.78 0.988964 1.034632 13074.40\n", + " 20-DEC-2024 0.252778 12638.89 0.987528 1.039145 12969.83\n", + " 20-MAR-2025 0.250000 12500.00 0.986110 1.043627 12864.15\n", + " 20-JUN-2025 0.258333 12916.67 0.984663 1.048229 13331.97\n" + ] + } + ], + "source": [ + "cds_contract.print_flows(valuation_date, issuer_curve)" + ] + }, + { + "attachments": {}, + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Valuation" + ] + }, + { + "cell_type": "code", + "execution_count": 23, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "FAIR CDS SPREAD 103.51694 bp\n" + ] + } + ], + "source": [ + "spd = cds_contract.par_spread(settlement_date, issuer_curve, recovery_rate) * 10000.0\n", + "print(\"FAIR CDS SPREAD %10.5f bp\"% spd)" + ] + }, + { + "cell_type": "code", + "execution_count": 24, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "(-279013.4379446856, -270541.2157224633, 724.3863562626066, 35.83825325872749)" + ] + }, + "execution_count": 24, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "cds_contract.value_fast_approx(settlement_date, 0.004, 0.01, 0.40, 0.40)" + ] + }, + { + "cell_type": "code", + "execution_count": 25, + "metadata": {}, + "outputs": [], + "source": [ + "v = cds_contract.value(settlement_date, issuer_curve, recovery_rate)" + ] + }, + { + "cell_type": "code", + "execution_count": 26, + "metadata": {}, + "outputs": [], + "source": [ + "full_pv = v['full_pv'] \n", + "clean_pv = v['clean_pv']" + ] + }, + { + "cell_type": "code", + "execution_count": 27, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "FULL VALUE -203180.08\n", + "CLEAN VALUE -194707.85\n" + ] + } + ], + "source": [ + "print(\"FULL VALUE %12.2f\"% full_pv)\n", + "print(\"CLEAN VALUE %12.2f\"% clean_pv)" + ] + }, + { + "cell_type": "code", + "execution_count": 28, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "CLEAN PRICE 119.473842\n" + ] + } + ], + "source": [ + "cleanp = cds_contract.clean_price(settlement_date, issuer_curve, recovery_rate)\n", + "print(\"CLEAN PRICE %12.6f\"% cleanp)" + ] + }, + { + "cell_type": "code", + "execution_count": 29, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "ACCRUED_DAYS 61.0\n" + ] + } + ], + "source": [ + "accrued_days = cds_contract.accrued_days()\n", + "print(\"ACCRUED_DAYS\", accrued_days)" + ] + }, + { + "cell_type": "code", + "execution_count": 30, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "ACCRUED_COUPON -8472.222222222224\n" + ] + } + ], + "source": [ + "accrued_interest = cds_contract.accrued_interest()\n", + "print(\"ACCRUED_COUPON\", accrued_interest)" + ] + }, + { + "cell_type": "code", + "execution_count": 29, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "PROTECTION_PV 48351.63655300625\n" + ] + } + ], + "source": [ + "prot_pv = cds_contract.protection_leg_pv(settlement_date, issuer_curve, recovery_rate)\n", + "print(\"PROTECTION_PV\", prot_pv)" + ] + }, + { + "cell_type": "code", + "execution_count": 30, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "PREMIUM_PV 247495.1684866482\n" + ] + } + ], + "source": [ + "premPV = cds_contract.premium_leg_pv(settlement_date, issuer_curve, recovery_rate)\n", + "print(\"PREMIUM_PV\", premPV)" + ] + }, + { + "cell_type": "code", + "execution_count": 31, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "{'full_rpv01': 4.949903369732964, 'clean_rpv01': 4.783236703066297}" + ] + }, + "execution_count": 31, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "cds_contract.risky_pv01(settlement_date, issuer_curve)" + ] + }, + { + "attachments": {}, + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Risk Measures" + ] + }, + { + "cell_type": "code", + "execution_count": 32, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "517.2947254180617" + ] + }, + "execution_count": 32, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "cds_contract.credit_dv01(settlement_date, issuer_curve, recovery_rate)" + ] + }, + { + "cell_type": "code", + "execution_count": 33, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "47.93812091532163" + ] + }, + "execution_count": 33, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "cds_contract.interest_dv01(settlement_date, issuer_curve, recovery_rate)" + ] + }, + { + "attachments": {}, + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Copyright (c) 2020 Dominic O'Kane" + ] + }, + { + "attachments": {}, + "cell_type": "markdown", + "metadata": {}, + "source": [] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.10.9" + } + }, + "nbformat": 4, + "nbformat_minor": 2 +} diff --git a/notebooks/products/credit/FINCDS_ForwardAndBackward.ipynb b/notebooks/products/credit/FINCDS_ForwardAndBackward.ipynb index 2da5f950..6e36279a 100644 --- a/notebooks/products/credit/FINCDS_ForwardAndBackward.ipynb +++ b/notebooks/products/credit/FINCDS_ForwardAndBackward.ipynb @@ -1,5 +1,13 @@ { "cells": [ + { + "attachments": {}, + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Analysis of CDS Schedule Generation" + ] + }, { "cell_type": "code", "execution_count": 1, @@ -10,7 +18,7 @@ "output_type": "stream", "text": [ "####################################################################\n", - "# FINANCEPY BETA Version 0.300 - This build: 30 Jun 2023 at 19:34 #\n", + "# FINANCEPY BETA Version 0.300 - This build: 02 Jul 2023 at 15:44 #\n", "# This software is distributed FREE AND WITHOUT ANY WARRANTY #\n", "# Report bugs as issues at https://github.com/domokane/FinancePy #\n", "####################################################################\n", @@ -23,11 +31,26 @@ "from financepy.products.credit import *" ] }, + { + "attachments": {}, + "cell_type": "markdown", + "metadata": {}, + "source": [] + }, { "cell_type": "code", "execution_count": 2, "metadata": {}, "outputs": [], + "source": [ + "set_date_format(DateFormatTypes.UK_LONGEST)" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": {}, + "outputs": [], "source": [ "effective_date = Date(20, 2, 2009)\n", "maturity_date = Date(20, 3, 2010)\n", @@ -36,7 +59,7 @@ }, { "cell_type": "code", - "execution_count": 3, + "execution_count": 4, "metadata": {}, "outputs": [ { @@ -44,9 +67,9 @@ "output_type": "stream", "text": [ "OBJECT TYPE: CDS\n", - "STEP-IN DATE: 20-FEB-2009\n", - "MATURITY: 20-MAR-2010\n", - "NOTIONAL: 1000000\n", + "STEP-IN DATE: FRI 20 FEB 2009\n", + "MATURITY: SAT 20 MAR 2010\n", + "NOTIONAL: 36000000\n", "RUNNING COUPON: 100.0bp\n", "DAYCOUNT: DayCountTypes.ACT_360\n", "FREQUENCY: FrequencyTypes.QUARTERLY\n", @@ -54,24 +77,24 @@ "BUSDAYRULE: BusDayAdjustTypes.FOLLOWING\n", "DATEGENRULE: DateGenRuleTypes.BACKWARD\n", "ACCRUED DAYS: 60.0\n", - "PAYMENT_DATE, YEAR_FRAC, FLOW, ACCRUAL_START, ACCTUAL_END\n", - "20-MAR-2009, 0.244444, 2444.444444, 22-DEC-2008, 19-MAR-2009\n", - "22-JUN-2009, 0.261111, 2611.111111, 20-MAR-2009, 21-JUN-2009\n", - "21-SEP-2009, 0.252778, 2527.777778, 22-JUN-2009, 20-SEP-2009\n", - "21-DEC-2009, 0.252778, 2527.777778, 21-SEP-2009, 20-DEC-2009\n", - "22-MAR-2010, 0.250000, 2500.000000, 21-DEC-2009, 20-MAR-2010\n" + "PAYMENT_DATE, YEAR_FRAC, ACCRUAL_START, ACCRUAL_END, FLOW\n", + "FRI 20 MAR 2009, 0.244444, MON 22 DEC 2008, THU 19 MAR 2009, 88000.000000\n", + "MON 22 JUN 2009, 0.261111, FRI 20 MAR 2009, SUN 21 JUN 2009, 94000.000000\n", + "MON 21 SEP 2009, 0.252778, MON 22 JUN 2009, SUN 20 SEP 2009, 91000.000000\n", + "MON 21 DEC 2009, 0.252778, MON 21 SEP 2009, SUN 20 DEC 2009, 91000.000000\n", + "MON 22 MAR 2010, 0.250000, MON 21 DEC 2009, SAT 20 MAR 2010, 90000.000000\n" ] } ], "source": [ "# Reference: https://www.cdsmodel.com/assets/cds-model/docs/Standard%20CDS%20Examples.pdf\n", - "cds_backward = CDS(effective_date, maturity_date, running_coupon, date_gen_rule_type=DateGenRuleTypes.BACKWARD)\n", + "cds_backward = CDS(effective_date, maturity_date, running_coupon, notional=36000000, date_gen_rule_type=DateGenRuleTypes.BACKWARD)\n", "print(cds_backward)" ] }, { "cell_type": "code", - "execution_count": 4, + "execution_count": 5, "metadata": {}, "outputs": [ { @@ -79,9 +102,9 @@ "output_type": "stream", "text": [ "OBJECT TYPE: CDS\n", - "STEP-IN DATE: 20-FEB-2009\n", - "MATURITY: 20-MAR-2010\n", - "NOTIONAL: 1000000\n", + "STEP-IN DATE: FRI 20 FEB 2009\n", + "MATURITY: SAT 20 MAR 2010\n", + "NOTIONAL: 36000000\n", "RUNNING COUPON: 100.0bp\n", "DAYCOUNT: DayCountTypes.ACT_360\n", "FREQUENCY: FrequencyTypes.QUARTERLY\n", @@ -89,19 +112,26 @@ "BUSDAYRULE: BusDayAdjustTypes.FOLLOWING\n", "DATEGENRULE: DateGenRuleTypes.FORWARD\n", "ACCRUED DAYS: 0.0\n", - "PAYMENT_DATE, YEAR_FRAC, FLOW, ACCRUAL_START, ACCTUAL_END\n", - "20-MAY-2009, 0.247222, 2472.222222, 20-FEB-2009, 19-MAY-2009\n", - "20-AUG-2009, 0.255556, 2555.555556, 20-MAY-2009, 19-AUG-2009\n", - "20-NOV-2009, 0.255556, 2555.555556, 20-AUG-2009, 19-NOV-2009\n", - "22-FEB-2010, 0.261111, 2611.111111, 20-NOV-2009, 21-FEB-2010\n", - "22-MAR-2010, 0.075000, 750.000000, 22-FEB-2010, 20-MAR-2010\n" + "PAYMENT_DATE, YEAR_FRAC, ACCRUAL_START, ACCRUAL_END, FLOW\n", + "WED 20 MAY 2009, 0.247222, FRI 20 FEB 2009, TUE 19 MAY 2009, 89000.000000\n", + "THU 20 AUG 2009, 0.255556, WED 20 MAY 2009, WED 19 AUG 2009, 92000.000000\n", + "FRI 20 NOV 2009, 0.255556, THU 20 AUG 2009, THU 19 NOV 2009, 92000.000000\n", + "MON 22 FEB 2010, 0.261111, FRI 20 NOV 2009, SUN 21 FEB 2010, 94000.000000\n", + "MON 22 MAR 2010, 0.075000, MON 22 FEB 2010, SAT 20 MAR 2010, 27000.000000\n" ] } ], "source": [ - "cds_forward = CDS(effective_date, maturity_date, running_coupon, date_gen_rule_type=DateGenRuleTypes.FORWARD)\n", + "cds_forward = CDS(effective_date, maturity_date, running_coupon, notional=36000000, date_gen_rule_type=DateGenRuleTypes.FORWARD)\n", "print(cds_forward)" ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [] } ], "metadata": { @@ -120,7 +150,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.10.6" + "version": "3.10.9" }, "orig_nbformat": 4 }, diff --git a/notebooks/products/credit/FINCDS_ValuingCDSCompareToMarkitV2.ipynb b/notebooks/products/credit/FINCDS_ValuingCDSCompareToMarkitV2.ipynb new file mode 100644 index 00000000..15560673 --- /dev/null +++ b/notebooks/products/credit/FINCDS_ValuingCDSCompareToMarkitV2.ipynb @@ -0,0 +1,548 @@ +{ + "cells": [ + { + "attachments": {}, + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Creating and Valuing a CDS Contract vs Markit" + ] + }, + { + "attachments": {}, + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Example CDS Valuation and comparison with market standard ISDA model on Markit website" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "metadata": {}, + "outputs": [], + "source": [ + "import numpy as np\n", + "import matplotlib.pyplot as plt" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "####################################################################\n", + "# FINANCEPY BETA Version 0.300 - This build: 04 Jul 2023 at 11:05 #\n", + "# This software is distributed FREE AND WITHOUT ANY WARRANTY #\n", + "# Report bugs as issues at https://github.com/domokane/FinancePy #\n", + "####################################################################\n", + "\n" + ] + } + ], + "source": [ + "from financepy.utils import *\n", + "from financepy.products.rates import *\n", + "from financepy.products.credit import *" + ] + }, + { + "attachments": {}, + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Creating a CDS Contract" + ] + }, + { + "attachments": {}, + "cell_type": "markdown", + "metadata": {}, + "source": [ + "This based on an example from Markit's CDS calculator website https://www.markit.com/markit.jsp?jsppage=pv.jsp" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": {}, + "outputs": [], + "source": [ + "valuation_date = Date(21, 11, 2019)\n", + "settlement_date = valuation_date.add_days(1)\n", + "maturity_date = Date(20, 12, 2024)\n", + "cdsCoupon = 0.050\n", + "notional = ONE_MILLION\n", + "long_protection = True\n", + "tradeDate = Date(9, 8, 2019)" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "metadata": {}, + "outputs": [], + "source": [ + "cds_contract = CDS(settlement_date, maturity_date, cdsCoupon, notional, long_protection)" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "OBJECT TYPE: CDS\n", + "STEP-IN DATE: 22-NOV-2019\n", + "MATURITY: 20-DEC-2024\n", + "NOTIONAL: 1000000\n", + "RUNNING COUPON: 500.0bp\n", + "DAYCOUNT: DayCountTypes.ACT_360\n", + "FREQUENCY: FrequencyTypes.QUARTERLY\n", + "CALENDAR: CalendarTypes.WEEKEND\n", + "BUSDAYRULE: BusDayAdjustTypes.FOLLOWING\n", + "DATEGENRULE: DateGenRuleTypes.BACKWARD\n", + "ACCRUED DAYS: 63.0\n", + "PAYMENT_DATE, YEAR_FRAC, ACCRUAL_START, ACCRUAL_END, FLOW\n", + "20-DEC-2019, 0.252778, 20-SEP-2019, 19-DEC-2019, 12638.888889\n", + "20-MAR-2020, 0.252778, 20-DEC-2019, 19-MAR-2020, 12638.888889\n", + "22-JUN-2020, 0.261111, 20-MAR-2020, 21-JUN-2020, 13055.555556\n", + "21-SEP-2020, 0.252778, 22-JUN-2020, 20-SEP-2020, 12638.888889\n", + "21-DEC-2020, 0.252778, 21-SEP-2020, 20-DEC-2020, 12638.888889\n", + "22-MAR-2021, 0.252778, 21-DEC-2020, 21-MAR-2021, 12638.888889\n", + "21-JUN-2021, 0.252778, 22-MAR-2021, 20-JUN-2021, 12638.888889\n", + "20-SEP-2021, 0.252778, 21-JUN-2021, 19-SEP-2021, 12638.888889\n", + "20-DEC-2021, 0.252778, 20-SEP-2021, 19-DEC-2021, 12638.888889\n", + "21-MAR-2022, 0.252778, 20-DEC-2021, 20-MAR-2022, 12638.888889\n", + "20-JUN-2022, 0.252778, 21-MAR-2022, 19-JUN-2022, 12638.888889\n", + "20-SEP-2022, 0.255556, 20-JUN-2022, 19-SEP-2022, 12777.777778\n", + "20-DEC-2022, 0.252778, 20-SEP-2022, 19-DEC-2022, 12638.888889\n", + "20-MAR-2023, 0.250000, 20-DEC-2022, 19-MAR-2023, 12500.000000\n", + "20-JUN-2023, 0.255556, 20-MAR-2023, 19-JUN-2023, 12777.777778\n", + "20-SEP-2023, 0.255556, 20-JUN-2023, 19-SEP-2023, 12777.777778\n", + "20-DEC-2023, 0.252778, 20-SEP-2023, 19-DEC-2023, 12638.888889\n", + "20-MAR-2024, 0.252778, 20-DEC-2023, 19-MAR-2024, 12638.888889\n", + "20-JUN-2024, 0.255556, 20-MAR-2024, 19-JUN-2024, 12777.777778\n", + "20-SEP-2024, 0.255556, 20-JUN-2024, 19-SEP-2024, 12777.777778\n", + "20-DEC-2024, 0.255556, 20-SEP-2024, 20-DEC-2024, 12777.777778\n" + ] + } + ], + "source": [ + "print(cds_contract)" + ] + }, + { + "attachments": {}, + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Build Ibor Curve" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "metadata": {}, + "outputs": [], + "source": [ + "dcType = DayCountTypes.ACT_360\n", + "depo1 = IborDeposit(settlement_date, \"1M\", 0.017156, dcType)\n", + "depo2 = IborDeposit(settlement_date, \"2M\", 0.018335, dcType)\n", + "depo3 = IborDeposit(settlement_date, \"3M\", 0.018988, dcType)\n", + "depo4 = IborDeposit(settlement_date, \"6M\", 0.018911, dcType)\n", + "depo5 = IborDeposit(settlement_date, \"12M\", 0.019093, dcType)\n", + "depos = [depo1,depo2,depo3,depo4,depo5]\n", + "\n", + "swapType = SwapTypes.PAY\n", + "dcType = DayCountTypes.THIRTY_E_360_ISDA\n", + "fixedFreq = FrequencyTypes.SEMI_ANNUAL\n", + "swap1 = IborSwap(settlement_date,\"2Y\",swapType,0.015630,fixedFreq,dcType)\n", + "swap2 = IborSwap(settlement_date,\"3Y\",swapType,0.015140,fixedFreq,dcType)\n", + "swap3 = IborSwap(settlement_date,\"4Y\",swapType,0.015065,fixedFreq,dcType)\n", + "swap4 = IborSwap(settlement_date,\"5Y\",swapType,0.015140,fixedFreq,dcType)\n", + "swap5 = IborSwap(settlement_date,\"6Y\",swapType,0.015270,fixedFreq,dcType)\n", + "swap6 = IborSwap(settlement_date,\"7Y\",swapType,0.015470,fixedFreq,dcType)\n", + "swap7 = IborSwap(settlement_date,\"8Y\",swapType,0.015720,fixedFreq,dcType)\n", + "swap8 = IborSwap(settlement_date,\"9Y\",swapType,0.016000,fixedFreq,dcType)\n", + "swap9 = IborSwap(settlement_date,\"10Y\",swapType,0.016285,fixedFreq,dcType)\n", + "swap10 = IborSwap(settlement_date,\"12Y\",swapType,0.01670,fixedFreq,dcType)\n", + "swaps = [swap1,swap2,swap3,swap4,swap5,swap6,swap7,swap8,swap9,swap10]\n", + "\n", + "libor_curve = IborSingleCurve(valuation_date, depos, [], swaps)" + ] + }, + { + "attachments": {}, + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Build a CDS Curve" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "metadata": {}, + "outputs": [], + "source": [ + "cdsFlatSpread = 0.0100" + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "metadata": {}, + "outputs": [], + "source": [ + "cds1 = CDS(settlement_date, \"1Y\", cdsFlatSpread)\n", + "cds2 = CDS(settlement_date, \"2Y\", cdsFlatSpread)\n", + "cds3 = CDS(settlement_date, \"3Y\", cdsFlatSpread)\n", + "cds4 = CDS(settlement_date, \"4Y\", cdsFlatSpread)\n", + "cds5 = CDS(settlement_date, \"5Y\", cdsFlatSpread)\n", + "cds6 = CDS(settlement_date, \"7Y\", cdsFlatSpread)\n", + "cds7 = CDS(settlement_date, \"10Y\", cdsFlatSpread)\n", + "cds8 = CDS(settlement_date, \"15Y\", cdsFlatSpread)" + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "metadata": {}, + "outputs": [], + "source": [ + "cdss = [cds1, cds2, cds3, cds4, cds5, cds6, cds7, cds8]" + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "metadata": {}, + "outputs": [], + "source": [ + "recovery_rate = 0.40" + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "metadata": {}, + "outputs": [], + "source": [ + "issuer_curve = CDSCurve(valuation_date, cdss, libor_curve, recovery_rate)" + ] + }, + { + "attachments": {}, + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Valuation Results" + ] + }, + { + "cell_type": "code", + "execution_count": 12, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "FAIR CDS SPREAD 100.00000 bp\n" + ] + } + ], + "source": [ + "spd = cds_contract.par_spread(valuation_date, issuer_curve, recovery_rate) * 10000.0\n", + "print(\"FAIR CDS SPREAD %10.5f bp\"% spd)" + ] + }, + { + "cell_type": "code", + "execution_count": 13, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "FULL VALUE -198532.56\n", + "CLEAN VALUE -189782.56\n" + ] + } + ], + "source": [ + "v = cds_contract.value(valuation_date, issuer_curve, recovery_rate)\n", + "full_pv = v['full_pv']\n", + "clean_pv = v['clean_pv']\n", + "\n", + "print(\"FULL VALUE %12.2f\"% full_pv)\n", + "print(\"CLEAN VALUE %12.2f\"% clean_pv)" + ] + }, + { + "attachments": {}, + "cell_type": "markdown", + "metadata": {}, + "source": [ + "MARKIT CALCULATE -198,551 for the FULL VALUE" + ] + }, + { + "cell_type": "code", + "execution_count": 14, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "CLEAN PRICE 118.979987\n" + ] + } + ], + "source": [ + "cleanp = cds_contract.clean_price(settlement_date, issuer_curve, recovery_rate)\n", + "print(\"CLEAN PRICE %12.6f\"% cleanp)" + ] + }, + { + "attachments": {}, + "cell_type": "markdown", + "metadata": {}, + "source": [ + "MARKIT CALCULATE 118.98%" + ] + }, + { + "cell_type": "code", + "execution_count": 15, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "ACCRUED_DAYS 63.0\n" + ] + } + ], + "source": [ + "accrued_days = cds_contract.accrued_days()\n", + "print(\"ACCRUED_DAYS\", accrued_days)" + ] + }, + { + "attachments": {}, + "cell_type": "markdown", + "metadata": {}, + "source": [ + "MARKIT CALCULATE 63" + ] + }, + { + "cell_type": "code", + "execution_count": 16, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "ACCRUED_COUPON -8750.0\n" + ] + } + ], + "source": [ + "accrued_interest = cds_contract.accrued_interest()\n", + "print(\"ACCRUED_COUPON\", accrued_interest)" + ] + }, + { + "attachments": {}, + "cell_type": "markdown", + "metadata": {}, + "source": [ + "MARKIT CALCULATE 8750" + ] + }, + { + "cell_type": "code", + "execution_count": 17, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "PROTECTION_PV 47449.853178301884\n" + ] + } + ], + "source": [ + "prot_pv = cds_contract.protection_leg_pv(settlement_date, issuer_curve, recovery_rate)\n", + "print(\"PROTECTION_PV\", prot_pv)" + ] + }, + { + "cell_type": "code", + "execution_count": 18, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "PREMIUM_PV 245999.71571231817\n" + ] + } + ], + "source": [ + "premPV = cds_contract.premium_leg_pv(settlement_date, issuer_curve, recovery_rate)\n", + "print(\"PREMIUM_PV\", premPV)" + ] + }, + { + "attachments": {}, + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Risk Measures" + ] + }, + { + "cell_type": "code", + "execution_count": 19, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Credit DV01 553.53\n" + ] + } + ], + "source": [ + "credit_dv01 = cds_contract.credit_dv01(settlement_date, issuer_curve, recovery_rate)\n", + "print(\"Credit DV01 %12.2f\"% credit_dv01)" + ] + }, + { + "attachments": {}, + "cell_type": "markdown", + "metadata": {}, + "source": [ + "MARKIT FOUND 554" + ] + }, + { + "cell_type": "code", + "execution_count": 20, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Interest DV01 48.82\n" + ] + } + ], + "source": [ + "interest_dv01 = cds_contract.interest_dv01(settlement_date, issuer_curve, recovery_rate)\n", + "print(\"Interest DV01 %12.2f\"% interest_dv01)" + ] + }, + { + "attachments": {}, + "cell_type": "markdown", + "metadata": {}, + "source": [ + "MARKIT FOUND 49" + ] + }, + { + "attachments": {}, + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Full Analysis " + ] + }, + { + "cell_type": "code", + "execution_count": 23, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "PAYMENT_DATE YEAR_FRAC FLOW DF SURV_PROB NPV\n", + " 20-DEC-2019 0.252778 12638.89 0.998620 0.998658 12604.51\n", + " 20-MAR-2020 0.252778 12638.89 0.993728 0.994459 12490.03\n", + " 22-JUN-2020 0.261111 13055.56 0.988864 0.990141 12782.88\n", + " 21-SEP-2020 0.252778 12638.89 0.984127 0.985978 12263.86\n", + " 21-DEC-2020 0.252778 12638.89 0.979968 0.981833 12160.69\n", + " 22-MAR-2021 0.252778 12638.89 0.977072 0.977711 12073.86\n", + " 21-JUN-2021 0.252778 12638.89 0.974185 0.973607 11987.65\n", + " 20-SEP-2021 0.252778 12638.89 0.971306 0.969521 11902.05\n", + " 20-DEC-2021 0.252778 12638.89 0.968270 0.965451 11815.05\n", + " 21-MAR-2022 0.252778 12638.89 0.964873 0.961400 11724.19\n", + " 20-JUN-2022 0.252778 12638.89 0.961488 0.957365 11634.03\n", + " 20-SEP-2022 0.255556 12777.78 0.958078 0.953304 11670.44\n", + " 20-DEC-2022 0.252778 12638.89 0.954666 0.949303 11454.22\n", + " 20-MAR-2023 0.250000 12500.00 0.951193 0.945363 11240.29\n", + " 20-JUN-2023 0.255556 12777.78 0.947656 0.941353 11398.78\n", + " 20-SEP-2023 0.255556 12777.78 0.944132 0.937360 11308.22\n", + " 20-DEC-2023 0.252778 12638.89 0.940615 0.933426 11096.88\n", + " 20-MAR-2024 0.252778 12638.89 0.937013 0.929510 11008.01\n", + " 20-JUN-2024 0.255556 12777.78 0.933386 0.925567 11038.86\n", + " 20-SEP-2024 0.255556 12777.78 0.929772 0.921641 10949.48\n", + " 20-DEC-2024 0.255556 12777.78 0.926176 0.917774 10861.37\n" + ] + } + ], + "source": [ + "cds_contract.print_flows(valuation_date, issuer_curve)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.10.9" + } + }, + "nbformat": 4, + "nbformat_minor": 2 +} diff --git a/notebooks/products/rates/FINOISCURVE_BuildingEONIACurveFollowingQLExample.ipynb b/notebooks/products/rates/FINOISCURVE_BuildingEONIACurveFollowingQLExample.ipynb index ca2a095d..25ee71ee 100644 --- a/notebooks/products/rates/FINOISCURVE_BuildingEONIACurveFollowingQLExample.ipynb +++ b/notebooks/products/rates/FINOISCURVE_BuildingEONIACurveFollowingQLExample.ipynb @@ -245,7 +245,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.9.12" + "version": "3.10.9" } }, "nbformat": 4, diff --git a/tests/test_FinBond.py b/tests/test_FinBond.py index c4930651..7ab53586 100644 --- a/tests/test_FinBond.py +++ b/tests/test_FinBond.py @@ -29,7 +29,7 @@ def test_bondtutor_example(): bond = Bond(issue_date, maturity_date, coupon, freq_type, accrualConvention, face) - full_price = bond.full_price_from_ytm(settlement_date, y) + full_price = bond.dirty_price_from_ytm(settlement_date, y) assert round(full_price, 4) == 108.7696 clean_price = bond.clean_price_from_ytm(settlement_date, y) assert round(clean_price, 4) == 106.5625 diff --git a/tests_golden/TestFinBond.py b/tests_golden/TestFinBond.py index 76fcd130..7cb578da 100644 --- a/tests_golden/TestFinBond.py +++ b/tests_golden/TestFinBond.py @@ -234,7 +234,8 @@ def test_Bond(): freq_type = FrequencyTypes.SEMI_ANNUAL settlement_date = Date(19, 9, 2012) face = ONE_MILLION - + ex_div_days = 0 + for accrual_type in DayCountTypes: if accrual_type == DayCountTypes.ZERO: continue @@ -250,7 +251,7 @@ def test_Bond(): coupon = bond['coupon'] / 100.0 clean_price = bond['mid'] bond = Bond(issueDt, maturityDt, - coupon, freq_type, accrual_type, 100) + coupon, freq_type, accrual_type, ex_div_days) ytm = bond.yield_to_maturity(settlement_date, clean_price) accrued_interest = bond._accrued_interest @@ -269,29 +270,35 @@ def test_Bond(): issue_date = Date(15, 7, 1990) maturity_date = Date(15, 7, 1997) coupon = 0.085 - face = ONE_MILLION + ex_div_days = 0 + face = 100.0 + freq_type = FrequencyTypes.SEMI_ANNUAL + bond = Bond(issue_date, maturity_date, - coupon, freq_type, accrualConvention, face) + coupon, freq_type, accrualConvention, ex_div_days) testCases.header("FIELD", "VALUE") - full_price = bond.full_price_from_ytm(settlement_date, y) - testCases.print("Full Price = ", full_price) + dirty_price = bond.dirty_price_from_ytm(settlement_date, y) + testCases.print("Dirty Price = ", dirty_price) + clean_price = bond.clean_price_from_ytm(settlement_date, y) testCases.print("Clean Price = ", clean_price) - accrued_interest = bond._accrued_interest + + accrued_interest = bond.accrued_interest(settlement_date, face) testCases.print("Accrued = ", accrued_interest) + ytm = bond.yield_to_maturity(settlement_date, clean_price) testCases.print("Yield to Maturity = ", ytm) - + bump = 1e-4 - priceBumpedUp = bond.full_price_from_ytm(settlement_date, y + bump) + priceBumpedUp = bond.dirty_price_from_ytm(settlement_date, y + bump) testCases.print("Price Bumped Up:", priceBumpedUp) - priceBumpedDn = bond.full_price_from_ytm(settlement_date, y - bump) + priceBumpedDn = bond.dirty_price_from_ytm(settlement_date, y - bump) testCases.print("Price Bumped Dn:", priceBumpedDn) - durationByBump = -(priceBumpedUp - full_price) / bump + durationByBump = -(priceBumpedUp - dirty_price) / bump testCases.print("Duration by Bump = ", durationByBump) duration = bond.dollar_duration(settlement_date, y) @@ -359,14 +366,15 @@ def test_Bond(): coupon = 0.02375 freq_type = FrequencyTypes.SEMI_ANNUAL accrual_type = DayCountTypes.ACT_ACT_ICMA + ex_div_days = 0 face = 1000000.0 - + bond = Bond(issue_date, maturity_date, coupon, freq_type, accrual_type, - face, + ex_div_days, CalendarTypes.UNITED_STATES) testCases.header("FIELD", "VALUE") @@ -387,15 +395,15 @@ def test_Bond(): YTMCalcType.US_TREASURY) testCases.print("US TREASURY Yield To Maturity = ", ytm) - full_price = bond.full_price_from_ytm(settlement_date, ytm, + dirty_price = bond.dirty_price_from_ytm(settlement_date, ytm, YTMCalcType.US_TREASURY) - testCases.print("Full Price = ", full_price) + testCases.print("Dirty Price = ", dirty_price) clean_price = bond.clean_price_from_ytm(settlement_date, ytm, YTMCalcType.US_TREASURY) testCases.print("Clean Price = ", clean_price) - accrued_interest = bond._accrued_interest + accrued_interest = bond.accrued_interest(settlement_date, face) testCases.print("Accrued = ", accrued_interest) accddays = bond._accrued_days @@ -424,10 +432,11 @@ def test_Bond(): coupon = 0.027 freq_type = FrequencyTypes.SEMI_ANNUAL accrual_type = DayCountTypes.THIRTY_E_360_ISDA - face = 100.0 - + ex_div_days = 0 + face = 1000000.0 + bond = Bond(issue_date, maturity_date, - coupon, freq_type, accrual_type, face) + coupon, freq_type, accrual_type, ex_div_days) testCases.header("FIELD", "VALUE") clean_price = 101.581564 @@ -447,8 +456,8 @@ def test_Bond(): YTMCalcType.US_TREASURY) testCases.print("US TREASURY Yield To Maturity", ytm) - full_price = bond.full_price_from_ytm(settlement_date, ytm) - testCases.print("Full Price", full_price) + dirty_price = bond.dirty_price_from_ytm(settlement_date, ytm) + testCases.print("Dirty Price", dirty_price) clean_price = bond.clean_price_from_ytm(settlement_date, ytm) testCases.print("Clean Price", clean_price) @@ -456,9 +465,9 @@ def test_Bond(): accddays = bond._accrued_days testCases.print("Accrued Days", accddays) - accrued_interest = bond._accrued_interest + accrued_interest = bond.accrued_interest(settlement_date, face) testCases.print("Accrued", accrued_interest) - + duration = bond.dollar_duration(settlement_date, ytm) testCases.print("Dollar Duration", duration) @@ -482,15 +491,15 @@ def test_BondExDividend(): freq_type = FrequencyTypes.SEMI_ANNUAL accrual_type = DayCountTypes.ACT_ACT_ICMA face = 100.0 - exDivDays = 7 + ex_div_days = 7 testCases.header("LABEL", "VALUE") calendar_type = CalendarTypes.UNITED_KINGDOM bond = Bond(issue_date, maturity_date, coupon, - freq_type, accrual_type, face) + freq_type, accrual_type, ex_div_days) settlement_date = Date(7, 9, 2003) - accrued = bond.calc_accrued_interest( - settlement_date, exDivDays, calendar_type) + accrued = bond.accrued_interest(settlement_date, face) + testCases.print("SettlementDate:", settlement_date) testCases.print("Accrued:", accrued) @@ -508,14 +517,14 @@ def test_BondExDividend(): calendar_type = CalendarTypes.UNITED_KINGDOM bond = Bond(issue_date, maturity_date, coupon, - freq_type, accrual_type, face) + freq_type, accrual_type, exDivDays) settlement_date = Date(25, 8, 2010) for _ in range(0, 13): settlement_date = settlement_date.add_days(1) - accrued = bond.calc_accrued_interest( - settlement_date, exDivDays, calendar_type) + accrued = bond.accrued_interest( + settlement_date, exDivDays) testCases.print(settlement_date, accrued) ############################################################################### @@ -556,8 +565,8 @@ def test_Bond_ror(): for row in df.itertuples(index=False): buy_date = Date(row.buy_date.day, row.buy_date.month, row.buy_date.year) sell_date = Date(row.sell_date.day, row.sell_date.month, row.sell_date.year) - buy_price = bond.full_price_from_ytm(buy_date, row.buy_ytm, YTMCalcType.US_STREET) - sell_price = bond.full_price_from_ytm(sell_date, row.sell_ytm, YTMCalcType.US_STREET) + buy_price = bond.dirty_price_from_ytm(buy_date, row.buy_ytm, YTMCalcType.US_STREET) + sell_price = bond.dirty_price_from_ytm(sell_date, row.sell_ytm, YTMCalcType.US_STREET) simple, irr, pnl = bond.calc_ror(buy_date, sell_date, row.buy_ytm, row.sell_ytm) testCases.print(row.bond_code, buy_date, row.buy_ytm, buy_price, sell_date, row.sell_ytm, sell_price, simple, irr) @@ -574,11 +583,11 @@ def test_Bond_eom(): coupon = 0.045 freq_type = FrequencyTypes.SEMI_ANNUAL accrual_type = DayCountTypes.ACT_ACT_ICMA - face = ONE_MILLION + ex_div_days = 0 - bond = Bond(issue_date, maturity_date, coupon, freq_type, accrual_type, face) + bond = Bond(issue_date, maturity_date, coupon, freq_type, accrual_type, ex_div_days) - ai = bond.calc_accrued_interest(settle_date) # should be 8406.593406 + ai = bond.accrued_interest(settle_date) # should be 8406.593406 ############################################################################### @@ -589,13 +598,13 @@ def test_key_rate_durations(): issue_date = Date(31, 7, 2022) maturity_date = Date(31, 7, 2027) coupon = 0.0275 - face = 100.0 + ex_div_days = 0 accrual_type, freq_type, settlementDays, exDiv, calendar = get_bond_market_conventions( BondMarkets.UNITED_STATES) bond = Bond(issue_date, maturity_date, coupon, - freq_type, accrual_type, face) + freq_type, accrual_type, ex_div_days) settlement_date = Date(24, 4, 2023) @@ -619,12 +628,13 @@ def test_key_rate_durations_Bloomberg_example(): maturity_date = Date(31, 7, 2027) coupon = 2.75/100.0 face = 100.0 - + ex_div_days = 0 + accrual_type, freq_type, settlementDays, exDiv, calendar = get_bond_market_conventions( BondMarkets.UNITED_STATES) bond = Bond(issue_date, maturity_date, coupon, - freq_type, accrual_type, face) + freq_type, accrual_type, ex_div_days) settlement_date = Date(24, 4, 2023) diff --git a/tests_golden/TestFinBondEmbeddedOptionBK.py b/tests_golden/TestFinBondEmbeddedOptionBK.py index 57da354f..7131cb8c 100644 --- a/tests_golden/TestFinBondEmbeddedOptionBK.py +++ b/tests_golden/TestFinBondEmbeddedOptionBK.py @@ -72,7 +72,7 @@ def test_BondEmbeddedOptionMATLAB(): putDate = putDate.add_months(1) testCases.header("BOND PRICE", "PRICE") - v = bond.clean_price_from_discount_curve(settlement_date, discount_curve) + v = bond.dirty_price_from_discount_curve(settlement_date, discount_curve) testCases.print("Bond Pure Price:", v) sigma = 0.01 # This volatility is very small for a BK process @@ -156,7 +156,7 @@ def test_BondEmbeddedOptionQUANTLIB(): put_dates, put_prices) testCases.header("BOND PRICE", "PRICE") - v = bond.clean_price_from_discount_curve(settlement_date, discount_curve) + v = bond.dirty_price_from_discount_curve(settlement_date, discount_curve) testCases.print("Bond Pure Price:", v) testCases.header("TIME", "NumTimeSteps", "BondWithOption", "BondPure") diff --git a/tests_golden/TestFinBondOptionBDTModel.py b/tests_golden/TestFinBondOptionBDTModel.py index 9a62f56e..d20a7859 100644 --- a/tests_golden/TestFinBondOptionBDTModel.py +++ b/tests_golden/TestFinBondOptionBDTModel.py @@ -57,7 +57,7 @@ def test_BondOption(): testCases.header("LABEL", "VALUE") - price = bond.full_price_from_discount_curve( + price = bond.dirty_price_from_discount_curve( settlement_date, discount_curve) testCases.print("Fixed Income Price:", price) @@ -89,7 +89,7 @@ def test_BondOption(): option_type = OptionTypes.AMERICAN_CALL - price = bond.full_price_from_discount_curve( + price = bond.dirty_price_from_discount_curve( settlement_date, discount_curve) testCases.header("LABEL", "VALUE") testCases.print("Fixed Income Price:", price) @@ -120,7 +120,7 @@ def test_BondOption(): option_type = OptionTypes.EUROPEAN_PUT - price = bond.full_price_from_discount_curve( + price = bond.dirty_price_from_discount_curve( settlement_date, discount_curve) for strike_price in strikes: @@ -147,7 +147,7 @@ def test_BondOption(): option_type = OptionTypes.AMERICAN_PUT - price = bond.full_price_from_discount_curve( + price = bond.dirty_price_from_discount_curve( settlement_date, discount_curve) for strike_price in strikes: @@ -254,7 +254,7 @@ def test_BondOptionAmericanConvergenceTWO(): expiry_date = settlement_date.add_tenor("18m") face = 100.0 - spotValue = bond.full_price_from_discount_curve( + spotValue = bond.dirty_price_from_discount_curve( settlement_date, discount_curve) testCases.header("LABEL", "VALUE") testCases.print("BOND PRICE", spotValue) diff --git a/tests_golden/TestFinBondOptionBKModel.py b/tests_golden/TestFinBondOptionBKModel.py index 5e3ec4ac..de09895e 100644 --- a/tests_golden/TestFinBondOptionBKModel.py +++ b/tests_golden/TestFinBondOptionBKModel.py @@ -56,7 +56,7 @@ def test_BondOption(): testCases.header("LABEL", "VALUE") - price = bond.full_price_from_discount_curve( + price = bond.dirty_price_from_discount_curve( settlement_date, discount_curve) testCases.print("Fixed Income Price:", price) @@ -90,7 +90,7 @@ def test_BondOption(): option_type = OptionTypes.AMERICAN_CALL - price = bond.full_price_from_discount_curve( + price = bond.dirty_price_from_discount_curve( settlement_date, discount_curve) testCases.header("LABEL", "VALUE") testCases.print("Fixed Income Price:", price) @@ -123,7 +123,7 @@ def test_BondOption(): option_type = OptionTypes.EUROPEAN_PUT - price = bond.full_price_from_discount_curve( + price = bond.dirty_price_from_discount_curve( settlement_date, discount_curve) for strike_price in strikes: @@ -152,7 +152,7 @@ def test_BondOption(): option_type = OptionTypes.AMERICAN_PUT - price = bond.full_price_from_discount_curve( + price = bond.dirty_price_from_discount_curve( settlement_date, discount_curve) for strike_price in strikes: @@ -262,7 +262,7 @@ def test_BondOptionAmericanConvergenceTWO(): expiry_date = settlement_date.add_tenor("18m") face = 100.0 - spotValue = bond.full_price_from_discount_curve( + spotValue = bond.dirty_price_from_discount_curve( settlement_date, discount_curve) testCases.header("LABEL", "VALUE") testCases.print("BOND PRICE", spotValue) @@ -373,7 +373,7 @@ def test_BondOptionZEROVOLConvergence(): dfExpiry = discount_curve.df(expiry_date) fwdCleanValue = bond.clean_price_from_discount_curve( expiry_date, discount_curve) - fwdFullValue = bond.full_price_from_discount_curve( + fwdFullValue = bond.dirty_price_from_discount_curve( expiry_date, discount_curve) # print("BOND FwdCleanBondPx", fwdCleanValue) # print("BOND FwdFullBondPx", fwdFullValue) diff --git a/tests_golden/TestFinBondOptionHWModel.py b/tests_golden/TestFinBondOptionHWModel.py index 2d36acd1..682486eb 100644 --- a/tests_golden/TestFinBondOptionHWModel.py +++ b/tests_golden/TestFinBondOptionHWModel.py @@ -397,7 +397,7 @@ def test_BondOptionZEROVOLConvergence(): dfExpiry = discount_curve.df(expiry_date) fwdCleanValue = bond.clean_price_from_discount_curve( expiry_date, discount_curve) -# fwdFullValue = bond.full_price_from_discount_curve(expiry_date, discount_curve) +# fwdFullValue = bond.dirty_price_from_discount_curve(expiry_date, discount_curve) # print("BOND FwdCleanBondPx", fwdCleanValue) # print("BOND FwdFullBondPx", fwdFullValue) # print("BOND Accrued:", bond._accrued_interest) @@ -478,7 +478,7 @@ def test_BondOptionDerivaGem(): europeanCallBondOption = BondOption(bond, expiry_date, strike_price, face, OptionTypes.EUROPEAN_CALL) cp = bond.clean_price_from_discount_curve(expiry_date, discount_curve) - fp = bond.full_price_from_discount_curve(expiry_date, discount_curve) + fp = bond.dirty_price_from_discount_curve(expiry_date, discount_curve) # print("Fixed Income Clean Price: %9.3f"% cp) # print("Fixed Income Full Price: %9.3f"% fp) diff --git a/tests_golden/TestFinBondZeroCoupon.py b/tests_golden/TestFinBondZeroCoupon.py index 42734665..a7fd5310 100644 --- a/tests_golden/TestFinBondZeroCoupon.py +++ b/tests_golden/TestFinBondZeroCoupon.py @@ -41,7 +41,7 @@ def test_bond_zero(): ytm = bond.yield_to_maturity(settlement_date, clean_price, YTMCalcType.ZERO) - accrued_interest = bond.calc_accrued_interest(settlement_date) + accrued_interest = bond.accrued_interest(settlement_date, 1.0) testCases.header('YTM', 'accrued') testCases.print(ytm, accrued_interest) @@ -61,8 +61,8 @@ def test_bond_zero_ror(): for row in df.itertuples(index=False): buy_date = Date(row.buy_date.day, row.buy_date.month, row.buy_date.year) sell_date = Date(row.sell_date.day, row.sell_date.month, row.sell_date.year) - buy_price = bond.full_price_from_ytm(buy_date, row.buy_ytm, YTMCalcType.ZERO) - sell_price = bond.full_price_from_ytm(sell_date, row.sell_ytm, YTMCalcType.ZERO) + buy_price = bond.dirty_price_from_ytm(buy_date, row.buy_ytm, YTMCalcType.ZERO) + sell_price = bond.dirty_price_from_ytm(sell_date, row.sell_ytm, YTMCalcType.ZERO) simple, irr, pnl = bond.calc_ror(buy_date, sell_date, row.buy_ytm, row.sell_ytm) testCases.print(row.bond_code, buy_date, row.buy_ytm, buy_price, sell_date, row.sell_ytm, sell_price, simple, irr) diff --git a/tests_golden/TestFinCDS.py b/tests_golden/TestFinCDS.py index 7865c5d9..2692a501 100644 --- a/tests_golden/TestFinCDS.py +++ b/tests_golden/TestFinCDS.py @@ -726,10 +726,10 @@ def test_CDSDateGeneration(): DateGenRuleTypes.BACKWARD) testCases.header("Flow Date", "AccrualFactor", "Flow") - num_flows = len(cds_contract._adjusted_dates) + num_flows = len(cds_contract._payment_dates) for n in range(0, num_flows): testCases.print(str( - cds_contract._adjusted_dates[n]), cds_contract._accrual_factors[n], + cds_contract._payment_dates[n]), cds_contract._accrual_factors[n], cds_contract._flows[n]) ########################################################################## diff --git a/tests_golden/TestFinCDSIndexOption.py b/tests_golden/TestFinCDSIndexOption.py index 41b958b6..9ed5f409 100644 --- a/tests_golden/TestFinCDSIndexOption.py +++ b/tests_golden/TestFinCDSIndexOption.py @@ -197,23 +197,30 @@ def test_full_priceCDSIndexOption(): "ABPAY", "ABREC") - for index in [20, 60]: + for index in [20, 40, 60]: ####################################################################### + print("Index", index) + cds_contracts = [] + for dt in indexMaturityDates: + cds = CDS(valuation_date, dt, index / 10000.0) cds_contracts.append(cds) - index_curve = CDSCurve(valuation_date, cds_contracts, - libor_curve, indexRecovery) + index_curve = CDSCurve(valuation_date, + cds_contracts, + libor_curve, + indexRecovery) if 1 == 1: indexSpreads = [index / 10000.0] * 4 indexPortfolio = CDSIndexPortfolio() + adjustedIssuerCurves = indexPortfolio.hazard_rate_adjust_intrinsic( valuation_date, issuer_curves, @@ -222,9 +229,11 @@ def test_full_priceCDSIndexOption(): indexMaturityDates, indexRecovery, tolerance) + else: indexSpread = index / 10000.0 + issuer_curve = buildFlatIssuerCurve(tradeDate, libor_curve, indexSpread, @@ -235,6 +244,8 @@ def test_full_priceCDSIndexOption(): adjustedIssuerCurves.append(issuer_curve) ####################################################################### + # Now loop over strikes + ####################################################################### for strike in [20, 60]: diff --git a/tests_golden/TestFinEquitySwap.py b/tests_golden/TestFinEquitySwap.py index 7a87fafe..2ac5ec72 100644 --- a/tests_golden/TestFinEquitySwap.py +++ b/tests_golden/TestFinEquitySwap.py @@ -21,6 +21,8 @@ from financepy.market.curves.discount_curve_flat import DiscountCurveFlat +testCases = FinTestCases(__file__, globalTestCaseMode) + def test_equity_swap_at_inception(): effective_date = Date(13, 2, 2018) diff --git a/tests_golden/TestFinInflationBond.py b/tests_golden/TestFinInflationBond.py index ba8b8b5b..cfc23082 100644 --- a/tests_golden/TestFinInflationBond.py +++ b/tests_golden/TestFinInflationBond.py @@ -38,13 +38,14 @@ def test_FinInflationBondBBG(): accrual_type = DayCountTypes.ACT_ACT_ICMA face = 100.0 baseCPIValue = 218.08532 - + ex_div_days = 0 + bond = FinInflationBond(issue_date, maturity_date, coupon, freq_type, accrual_type, - face, + ex_div_days, baseCPIValue) testCases.header("FIELD", "VALUE") @@ -75,8 +76,8 @@ def test_FinInflationBondBBG(): testCases.print("US TREASURY REAL Yield To Maturity = ", ytm) - full_price = bond.full_price_from_ytm(settlement_date, ytm) - testCases.print("Full Price from REAL YTM = ", full_price) + dirty_price = bond.dirty_price_from_ytm(settlement_date, ytm) + testCases.print("Dirty Price from REAL YTM = ", dirty_price) clean_price = bond.clean_price_from_ytm(settlement_date, ytm) testCases.print("Clean Price from Real YTM = ", clean_price) @@ -98,8 +99,9 @@ def test_FinInflationBondBBG(): clean_price = bond.clean_price_from_ytm(settlement_date, ytm) testCases.print("Clean Price from Real YTM = ", clean_price) - inflationAccd = bond.calc_inflation_accrued_interest(settlement_date, - refCPIValue) + inflationAccd = bond.inflation_accrued_interest(settlement_date, + face, + refCPIValue) testCases.print("Inflation Accrued = ", inflationAccd) @@ -111,7 +113,10 @@ def test_FinInflationBondBBG(): testCases.print("Flat Price from Real YTM = ", clean_price) + face = 100.0 + principal = bond.inflation_principal(settlement_date, + face, ytm, refCPIValue, YTMCalcType.US_TREASURY) @@ -247,12 +252,14 @@ def test_FinInflationBondStack(): ########################################################################### + ex_div_days = 0 + bond = FinInflationBond(issue_date, maturity_date, coupon, freq_type, accrual_type, - face, + ex_div_days, baseCPIValue) testCases.header("FIELD", "VALUE") @@ -285,8 +292,8 @@ def test_FinInflationBondStack(): testCases.print("US TREASURY REAL Yield To Maturity = ", ytm) - full_price = bond.full_price_from_ytm(settlement_date, ytm) - testCases.print("Full Price from REAL YTM = ", full_price) + dirty_price = bond.dirty_price_from_discount_curve(settlement_date, ytm) + testCases.print("Dirty Price from REAL YTM = ", dirty_price) clean_price = bond.clean_price_from_ytm(settlement_date, ytm) testCases.print("Clean Price from Real YTM = ", clean_price) diff --git a/tests_golden/golden/TestFinCDS_GOLDEN.testLog b/tests_golden/golden/TestFinCDS_GOLDEN.testLog index 75c668e9..dff1b165 100644 --- a/tests_golden/golden/TestFinCDS_GOLDEN.testLog +++ b/tests_golden/golden/TestFinCDS_GOLDEN.testLog @@ -42,7 +42,6 @@ RESULTS,CLEAN APPROX VALUE,-187520.03419100, RESULTS,APPROX CREDIT DV01,534.99729419, RESULTS,APPROX INTEREST DV01,44.63274893, HEADER,Flow Date,AccrualFactor,Flow, -RESULTS,20-JUN-2019,0.00000000,0.00000000, RESULTS,20-SEP-2019,0.25555556,2555.55555556, RESULTS,20-DEC-2019,0.25277778,2527.77777778, RESULTS,20-MAR-2020,0.25277778,2527.77777778,